<commit_before>package command

import (
	"fmt"
	"flag"
//	"beam.io/beam/client"
)

type deviceData struct {
	ProjectId     uint64 `json:"project_id"`
	DeviceId      string `json:"device_id,omitempty"`
	DeviceName    string `json:"device_name,omitempty"`
	DeviceType    string `json:"device_type,omitempty"`
	Created       string `json:"created,omitempty"`
	// Private fields, not marshalled into JSON
	isUpdate      bool
	isGet         bool
	isDelete      bool
	isList        bool
}

func (d *deviceData) IsValid() bool {
	if d.isUpdate {
		return d.ProjectId != 0 ||
			len(d.DeviceName) > 0 ||
			len(d.DeviceType) > 0
	} else if d.isGet || d.isDelete {
		return len(d.DeviceId) > 0
	}
	return d.ProjectId != 0
}

func NewDevicesCommand() *Command {
	cmd := &Command {
		Name: "device",
		Usage: "Create, get, or delete devices",
		SubCommands: Mux {
			"get": newGetDeviceCmd(),
			"create": newCreateDeviceCmd(),
			"update": newUpdateDeviceCmd(),
			"list": newListDevicesCmd(),
			"delete": newDeleteDeviceCmd(),
		},
	}

	return cmd
}

func newCreateOrUpdateDeviceCmd(update bool, name string, action CommandAction) *Command {

	device := deviceData{
		isUpdate: update,
	}

	flags := flag.NewFlagSet("device", flag.ExitOnError)
	flags.Uint64Var(&device.ProjectId, "projectId", 0, "The project associated with the device")
	flags.StringVar(&device.DeviceName, "name", "",	"The device name")
	flags.StringVar(&device.DeviceId, "id", "", "The device's identifier")
	flags.StringVar(&device.DeviceType, "type", "", "The type of device")

	cmd := &Command {
		Name: name,
		ApiPath: "/v1/devices",
		Usage: name + " device",
		Data: &device,
		Flags: flags,
		Action: action,
	}

	return cmd
}

func newCreateDeviceCmd() *Command {
	return newCreateOrUpdateDeviceCmd(false, "create", createDevice)
}

func newUpdateDeviceCmd() *Command {
	return newCreateOrUpdateDeviceCmd(true, "update", updateDevice)
}

func createDevice(c *Command, ctx *Context) error {

	_, err := ctx.Client.
		Post(c.ApiPath).
		Body(c.Data).
		Expect(201).
		ResponseBody(c.Data).
		ResponseBodyHandler(func(body interface{}) error {

		device := body.(*deviceData)
		fmt.Printf("The new device ID is %v\n",
			device.DeviceId)

		return nil
	}).Execute();

	return err
}

func updateDevice(c *Command, ctx *Context) error {

	device := c.Data.(*deviceData)

	rsp, err := ctx.Client.
		Patch(c.ApiPath + "/" + device.DeviceId).
		Body(c.Data).
		Expect(200).
		Execute();

	if err == nil {
		fmt.Println("Device successfully updated")
	} else if rsp.Http().StatusCode == 204 {
		fmt.Println("Device not modified")
		return nil
	}

	return err
}

func newGetDeviceCmd() *Command {

	device := deviceData{
		isGet: true,
	}

	cmd := &Command {
		Name: "get",
		ApiPath: "/v1/devices",
		Usage: "get device information",
		Data: &device,
		Flags: flag.NewFlagSet("get", flag.ExitOnError),
		Action: getDevice,
	}

	cmd.Flags.StringVar(&device.DeviceId, "id", "", "The ID of the device to query (REQUIRED)")

	return cmd
}

func getDevice(c *Command, ctx *Context) error {

	device := c.Data.(*deviceData)

	_, err := ctx.Client.Get(c.ApiPath + "/" + device.DeviceId).
		Expect(200).
		ResponseBody(device).
		ResponseBodyHandler(func(interface{}) error {

		fmt.Printf("Device name: %v\n" +
			"Device ID: %v\n" +
			"Project ID: %v\n" +
			"Type: %v\n" +
			"Created: %v\n",
			device.DeviceName,
			device.DeviceId,
			device.ProjectId,
			device.DeviceType,
			device.Created)

		return nil
	}).Execute();

	return err
}

func newListDevicesCmd() *Command {

	device := deviceData{
		isList: true,
	}

	cmd := &Command {
		Name: "list",
		ApiPath: "/v1/devices",
		Usage: "list devices",
		Data: &device,
		Flags: flag.NewFlagSet("get", flag.ExitOnError),
		Action: getDevice,
	}

	cmd.Flags.Uint64Var(&device.ProjectId, "id", 0, "List devices in this project (REQUIRED)")

	return cmd
}

func listDevices(c *Command, ctx *Context) error {

	type deviceList struct {
		Devices []deviceData
	}

	_, err := ctx.Client.
		Get(c.ApiPath).
		ParamUint64("project_id", c.Data.(*deviceData).ProjectId).
		Expect(200).
		ResponseBody(new (deviceList)).
		ResponseBodyHandler(func(body interface{}) error {

		list := body.(*deviceList)

		fmt.Printf("Devices in project %v\n", c.Data.(*deviceData).ProjectId)

		for _, device := range(list.Devices) {

			fmt.Printf("\nName: %v\n" +
				"Device ID: %v\n" +
				"Type: %v\n" +
				"Created: %v\n",
				device.DeviceName,
				device.DeviceId,
				device.DeviceType,
				device.Created)
		}

		return nil
	}).Execute()

	return err
}

func newDeleteDeviceCmd() *Command {

	device := deviceData{
		isDelete: true,
	}

	cmd := &Command {
		Name: "delete",
		ApiPath: "/v1/devices",
		Usage: "delete device",
		Data: &device,
		Flags: flag.NewFlagSet("delete", flag.ExitOnError),
		Action: deleteDevice,
	}

	cmd.Flags.StringVar(&device.DeviceId, "id", "", "The ID of the device to delete (REQUIRED)")

	return cmd
}

func deleteDevice(c *Command, ctx *Context) error {

	_, err := ctx.Client.
		Delete(c.ApiPath + "/" + c.Data.(*deviceData).DeviceId).
		Expect(204).
		Execute()

	if err == nil {
		fmt.Println("Device successfully deleted")
	}

	return nil
}
<commit_msg>Return error from deleteDevice()<commit_after>package command

import (
	"fmt"
	"flag"
//	"beam.io/beam/client"
)

type deviceData struct {
	ProjectId     uint64 `json:"project_id"`
	DeviceId      string `json:"device_id,omitempty"`
	DeviceName    string `json:"device_name,omitempty"`
	DeviceType    string `json:"device_type,omitempty"`
	Created       string `json:"created,omitempty"`
	// Private fields, not marshalled into JSON
	isUpdate      bool
	isGet         bool
	isDelete      bool
	isList        bool
}

func (d *deviceData) IsValid() bool {
	if d.isUpdate {
		return d.ProjectId != 0 ||
			len(d.DeviceName) > 0 ||
			len(d.DeviceType) > 0
	} else if d.isGet || d.isDelete {
		return len(d.DeviceId) > 0
	}
	return d.ProjectId != 0
}

func NewDevicesCommand() *Command {
	cmd := &Command {
		Name: "device",
		Usage: "Create, get, or delete devices",
		SubCommands: Mux {
			"get": newGetDeviceCmd(),
			"create": newCreateDeviceCmd(),
			"update": newUpdateDeviceCmd(),
			"list": newListDevicesCmd(),
			"delete": newDeleteDeviceCmd(),
		},
	}

	return cmd
}

func newCreateOrUpdateDeviceCmd(update bool, name string, action CommandAction) *Command {

	device := deviceData{
		isUpdate: update,
	}

	flags := flag.NewFlagSet("device", flag.ExitOnError)
	flags.Uint64Var(&device.ProjectId, "projectId", 0, "The project associated with the device")
	flags.StringVar(&device.DeviceName, "name", "",	"The device name")
	flags.StringVar(&device.DeviceId, "id", "", "The device's identifier")
	flags.StringVar(&device.DeviceType, "type", "", "The type of device")

	cmd := &Command {
		Name: name,
		ApiPath: "/v1/devices",
		Usage: name + " device",
		Data: &device,
		Flags: flags,
		Action: action,
	}

	return cmd
}

func newCreateDeviceCmd() *Command {
	return newCreateOrUpdateDeviceCmd(false, "create", createDevice)
}

func newUpdateDeviceCmd() *Command {
	return newCreateOrUpdateDeviceCmd(true, "update", updateDevice)
}

func createDevice(c *Command, ctx *Context) error {

	_, err := ctx.Client.
		Post(c.ApiPath).
		Body(c.Data).
		Expect(201).
		ResponseBody(c.Data).
		ResponseBodyHandler(func(body interface{}) error {

		device := body.(*deviceData)
		fmt.Printf("The new device ID is %v\n",
			device.DeviceId)

		return nil
	}).Execute();

	return err
}

func updateDevice(c *Command, ctx *Context) error {

	device := c.Data.(*deviceData)

	rsp, err := ctx.Client.
		Patch(c.ApiPath + "/" + device.DeviceId).
		Body(c.Data).
		Expect(200).
		Execute();

	if err == nil {
		fmt.Println("Device successfully updated")
	} else if rsp.Http().StatusCode == 204 {
		fmt.Println("Device not modified")
		return nil
	}

	return err
}

func newGetDeviceCmd() *Command {

	device := deviceData{
		isGet: true,
	}

	cmd := &Command {
		Name: "get",
		ApiPath: "/v1/devices",
		Usage: "get device information",
		Data: &device,
		Flags: flag.NewFlagSet("get", flag.ExitOnError),
		Action: getDevice,
	}

	cmd.Flags.StringVar(&device.DeviceId, "id", "", "The ID of the device to query (REQUIRED)")

	return cmd
}

func getDevice(c *Command, ctx *Context) error {

	device := c.Data.(*deviceData)

	_, err := ctx.Client.Get(c.ApiPath + "/" + device.DeviceId).
		Expect(200).
		ResponseBody(device).
		ResponseBodyHandler(func(interface{}) error {

		fmt.Printf("Device name: %v\n" +
			"Device ID: %v\n" +
			"Project ID: %v\n" +
			"Type: %v\n" +
			"Created: %v\n",
			device.DeviceName,
			device.DeviceId,
			device.ProjectId,
			device.DeviceType,
			device.Created)

		return nil
	}).Execute();

	return err
}

func newListDevicesCmd() *Command {

	device := deviceData{
		isList: true,
	}

	cmd := &Command {
		Name: "list",
		ApiPath: "/v1/devices",
		Usage: "list devices",
		Data: &device,
		Flags: flag.NewFlagSet("get", flag.ExitOnError),
		Action: getDevice,
	}

	cmd.Flags.Uint64Var(&device.ProjectId, "id", 0, "List devices in this project (REQUIRED)")

	return cmd
}

func listDevices(c *Command, ctx *Context) error {

	type deviceList struct {
		Devices []deviceData
	}

	_, err := ctx.Client.
		Get(c.ApiPath).
		ParamUint64("project_id", c.Data.(*deviceData).ProjectId).
		Expect(200).
		ResponseBody(new (deviceList)).
		ResponseBodyHandler(func(body interface{}) error {

		list := body.(*deviceList)

		fmt.Printf("Devices in project %v\n", c.Data.(*deviceData).ProjectId)

		for _, device := range(list.Devices) {

			fmt.Printf("\nName: %v\n" +
				"Device ID: %v\n" +
				"Type: %v\n" +
				"Created: %v\n",
				device.DeviceName,
				device.DeviceId,
				device.DeviceType,
				device.Created)
		}

		return nil
	}).Execute()

	return err
}

func newDeleteDeviceCmd() *Command {

	device := deviceData{
		isDelete: true,
	}

	cmd := &Command {
		Name: "delete",
		ApiPath: "/v1/devices",
		Usage: "delete device",
		Data: &device,
		Flags: flag.NewFlagSet("delete", flag.ExitOnError),
		Action: deleteDevice,
	}

	cmd.Flags.StringVar(&device.DeviceId, "id", "", "The ID of the device to delete (REQUIRED)")

	return cmd
}

func deleteDevice(c *Command, ctx *Context) error {

	_, err := ctx.Client.
		Delete(c.ApiPath + "/" + c.Data.(*deviceData).DeviceId).
		Expect(204).
		Execute()

	if err == nil {
		fmt.Println("Device successfully deleted")
	}

	return err
}
<|endoftext|>


<commit_before>package command

import (
	"github.com/codegangsta/cli"
	"github.com/farmer-project/farmer-cli/api"
	"github.com/farmer-project/farmer/farmer"
	"github.com/olekukonko/tablewriter"
	"os"
	"strings"
)

func InspectCmd() cli.Command {
	return cli.Command{
		Name:        "inspect",
		Usage:       "<boxname>",
		Description: "Displays box's details such as repository Url, current branch specifier, state, etc.",
		Action:      inspectAction,
	}
}

func inspectAction(context *cli.Context) {
	if !context.Args().Present() {
		println("You must specify a 'name' for the box you want to create.\nSee 'farmer create --help' for more info.")
		return
	}

	box := &farmer.Box{}
	if err := api.Get("/boxes/"+context.Args().First(), nil, box); err != nil {
		println(err.Error())
		return
	}

	generateBoxTable(box)
}

func generateBoxTable(box *farmer.Box) {
	data := [][]string{}
	data = append(data, []string{
		box.Name,
		box.RepoUrl,
		box.Pathspec,
		box.Image,
		box.Status,
		strings.Join(box.Ports, ","),
		domainsToString(box.Domains),
	})

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{
		"Name",
		"Repository",
		"Pathspec",
		"Image",
		"Status",
		"Ports",
		"Domains",
	})
	table.SetBorder(true)
	table.AppendBulk(data)
	table.Render()
}

func domainsToString(domains []farmer.Domain) string {
	var output string
	for _, domain := range domains {
		output += domain.Url + "->" + domain.Port + ", "
	}

	return output
}
<commit_msg>feaure(inspect): Show box home directory<commit_after>package command

import (
	"github.com/codegangsta/cli"
	"github.com/farmer-project/farmer-cli/api"
	"github.com/farmer-project/farmer/farmer"
	"github.com/olekukonko/tablewriter"
	"os"
	"strings"
)

func InspectCmd() cli.Command {
	return cli.Command{
		Name:        "inspect",
		Usage:       "<boxname>",
		Description: "Displays box's details such as repository Url, current branch specifier, state, etc.",
		Action:      inspectAction,
	}
}

func inspectAction(context *cli.Context) {
	if !context.Args().Present() {
		println("You must specify a 'name' for the box you want to create.\nSee 'farmer create --help' for more info.")
		return
	}

	box := &farmer.Box{}
	if err := api.Get("/boxes/"+context.Args().First(), nil, box); err != nil {
		println(err.Error())
		return
	}

	generateBoxTable(box)
}

func generateBoxTable(box *farmer.Box) {
	data := [][]string{}
	data = append(data, []string{
		box.Status,
		box.Name,
		box.RepoUrl,
		box.Pathspec,
		box.Image,
		box.Home,
		strings.Join(box.Ports, ","),
		domainsToString(box.Domains),
	})

	table := tablewriter.NewWriter(os.Stdout)
	table.SetHeader([]string{
		"Status",
		"Name",
		"Repository",
		"Pathspec",
		"Image",
		"Home",
		"Ports",
		"Domains",
	})
	table.SetBorder(true)
	table.AppendBulk(data)
	table.Render()
}

func domainsToString(domains []farmer.Domain) string {
	var output string
	for _, domain := range domains {
		output += domain.Url + "->" + domain.Port + ", "
	}

	return output
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"

	yaml "gopkg.in/yaml.v2"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	stks "github.com/daidokoro/qaz/stacks"

	"github.com/daidokoro/hcl"
)

// Configure parses the config file abd setos stacjs abd ebv
func Configure(confSource string, conf string) (err error) {

	// set config session
	config.Session, err = GetSession()
	if err != nil {
		return
	}

	if conf == "" {
		// utilise FetchSource to get sources
		if err = stks.FetchSource(confSource, &config); err != nil {
			return err
		}
	} else {
		config.String = conf
	}

	// execute config Functions
	if err = config.CallFunctions(GenTimeFunctions); err != nil {
		return fmt.Errorf("failed to run template functions in config: %s", err)
	}

	log.Debug("checking Config for HCL format...")
	if err = hcl.Unmarshal([]byte(config.String), &config); err != nil {
		// fmt.Println(err)
		log.Debug("failed to parse hcl... moving to JSON/YAML... error: %v", err)
		if err = yaml.Unmarshal([]byte(config.String), &config); err != nil {
			return err
		}
	}

	log.Debug("Config File Read: %s", config.Project)

	// stacks = make(map[string]*stks.Stack)

	// Get Stack Values
	for s, v := range config.Stacks {
		stacks.Add(s, &stks.Stack{
			Name:           s,
			Profile:        v.Profile,
			Region:         v.Region,
			DependsOn:      v.DependsOn,
			Policy:         v.Policy,
			Source:         v.Source,
			Stackname:      v.Name,
			Bucket:         v.Bucket,
			Role:           v.Role,
			DeployDelims:   &config.DeployDelimiter,
			GenDelims:      &config.GenerateDelimiter,
			TemplateValues: config.Vars(),
			GenTimeFunc:    &GenTimeFunctions,
			DeployTimeFunc: &DeployTimeFunctions,
			Project:        &config.Project,
			Timeout:        v.Timeout,
		})

		stacks.MustGet(s).SetStackName()

		// set session
		stacks.MustGet(s).Session, err = GetSession(func(opts *session.Options) {
			if stacks.MustGet(s).Profile != "" {
				opts.Profile = stacks.MustGet(s).Profile
			}

			// use config region
			if config.Region != "" {
				opts.Config.Region = aws.String(config.Region)
			}

			// stack region trumps all other regions if-set
			if stacks.MustGet(s).Region != "" {
				opts.Config.Region = aws.String(stacks.MustGet(s).Region)
			}

			return
		})

		if err != nil {
			return
		}

		// stacks.MustGet(s).Session = sess

		// set parameters and tags, if any
		config.Parameters(stacks.MustGet(s)).Tags(stacks.MustGet(s))

	}

	return
}
<commit_msg>added notification-arn to stack map loading<commit_after>package commands

import (
	"fmt"

	yaml "gopkg.in/yaml.v2"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	stks "github.com/daidokoro/qaz/stacks"

	"github.com/daidokoro/hcl"
)

// Configure parses the config file abd setos stacjs abd ebv
func Configure(confSource string, conf string) (err error) {

	// set config session
	config.Session, err = GetSession()
	if err != nil {
		return
	}

	if conf == "" {
		// utilise FetchSource to get sources
		if err = stks.FetchSource(confSource, &config); err != nil {
			return err
		}
	} else {
		config.String = conf
	}

	// execute config Functions
	if err = config.CallFunctions(GenTimeFunctions); err != nil {
		return fmt.Errorf("failed to run template functions in config: %s", err)
	}

	log.Debug("checking Config for HCL format...")
	if err = hcl.Unmarshal([]byte(config.String), &config); err != nil {
		// fmt.Println(err)
		log.Debug("failed to parse hcl... moving to JSON/YAML... error: %v", err)
		if err = yaml.Unmarshal([]byte(config.String), &config); err != nil {
			return err
		}
	}

	log.Debug("Config File Read: %s", config.Project)

	// stacks = make(map[string]*stks.Stack)

	// Get Stack Values
	for s, v := range config.Stacks {
		stacks.Add(s, &stks.Stack{
			Name:             s,
			Profile:          v.Profile,
			Region:           v.Region,
			DependsOn:        v.DependsOn,
			Policy:           v.Policy,
			Source:           v.Source,
			Stackname:        v.Name,
			Bucket:           v.Bucket,
			Role:             v.Role,
			DeployDelims:     &config.DeployDelimiter,
			GenDelims:        &config.GenerateDelimiter,
			TemplateValues:   config.Vars(),
			GenTimeFunc:      &GenTimeFunctions,
			DeployTimeFunc:   &DeployTimeFunctions,
			Project:          &config.Project,
			Timeout:          v.Timeout,
			NotificationARNs: v.NotificationARNs,
		})

		stacks.MustGet(s).SetStackName()

		// set session
		stacks.MustGet(s).Session, err = GetSession(func(opts *session.Options) {
			if stacks.MustGet(s).Profile != "" {
				opts.Profile = stacks.MustGet(s).Profile
			}

			// use config region
			if config.Region != "" {
				opts.Config.Region = aws.String(config.Region)
			}

			// stack region trumps all other regions if-set
			if stacks.MustGet(s).Region != "" {
				opts.Config.Region = aws.String(stacks.MustGet(s).Region)
			}

			return
		})

		if err != nil {
			return
		}

		// stacks.MustGet(s).Session = sess

		// set parameters and tags, if any
		config.Parameters(stacks.MustGet(s)).Tags(stacks.MustGet(s))

	}

	return
}
<|endoftext|>


<commit_before>//
package commands

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/kardianos/osext"
	"github.com/nanobox-io/nanobox-golang-stylish"
	"github.com/spf13/cobra"

	"github.com/nanobox-io/nanobox/config"
	"github.com/nanobox-io/nanobox/util"
	fileutil "github.com/nanobox-io/nanobox/util/file"
	printutil "github.com/nanobox-io/nanobox/util/print"
)

var updateCmd = &cobra.Command{
	Use:   "update",
	Short: "Updates the CLI to the newest available version",
	Long:  ``,

	Run: update,
}

// update
func update(ccmd *cobra.Command, args []string) {

	update, err := updatable()
	if err != nil {
		Config.Error("Unable to determing if updates are available", err.Error())
		return
	}

	// if the md5s don't match or it's been forced, update
	switch {
	case update, config.Force:
		if err := runUpdate(); err != nil {
			fmt.Printf("ERR?? %#v\n", err)
			if _, ok := err.(*os.LinkError); ok {
				fmt.Println(`Nanobox was unable to update, try again with admin privilege (ex. "sudo nanobox update")`)
			} else {
				Config.Fatal("[commands/update] runUpdate() failed", err.Error())
			}
		}
	default:
		fmt.Printf(stylish.SubBullet("[√] Nanobox is up-to-date"))
	}
}

// Update
func Update() error {

	update, err := updatable()
	if err != nil {
		Config.Error("Unable to determine if updates are available.", err.Error())
		return err
	}

	// stat the update file to get ModTime(); an error here means the file doesn't
	// exist. This is highly unlikely as the file is created if it doesn't exist
	// each time the CLI is run.
	fi, _ := os.Stat(config.UpdateFile)

	// if the md5s don't match and it's 'time' for an update (14 days), OR a force
	// update is issued, update
	if update && time.Since(fi.ModTime()).Hours() >= 336.0 {

		//
		switch printutil.Prompt("Nanobox is out of date, would you like to update it now (y/N)? ") {

		// don't update by default, assuming they'll just do it manually, prompting
		// again after 14 days
		default:
			fmt.Println("You can manually update at any time with 'nanobox update'.")
			return touchUpdate()

		// if yes continue to update
		case "Yes", "yes", "Y", "y":
			if err := runUpdate(); err != nil {
				if _, ok := err.(*os.LinkError); ok {
					fmt.Println(`Nanobox was unable to update, try again with admin privilege (ex. "sudo nanobox update")`)
				} else {
					Config.Fatal("[commands/update] runUpdate() failed", err.Error())
				}
			}
		}
	}

	return nil
}

// updatable
func updatable() (bool, error) {

	//
	path, err := osext.Executable()
	if err != nil {
		config.Log.Fatal("[commands/update] osext.Executable() failed", err.Error())
	}

	// check the current cli md5 against the remote md5; os.Args[0] is used as the
	// final interpolation to determine standard/dev versions
	md5 := fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/%v.md5", config.OS, config.ARCH, filepath.Base(os.Args[0]))

	// check the path of the md5 current executing cli against the remote md5
	match, err := Util.MD5sMatch(path, md5)
	if err != nil {
		return false, err
	}

	return !match, nil
}

// runUpdate attemtps to update using the updater; if it's not available nanobox
// will download it and then run it.
func runUpdate() error {

	//
	path, err := osext.Executable()
	if err != nil {
		config.Log.Fatal("[commands/update] osext.Executable() failed", err.Error())
	}

	// get the directory of the current executing cli
	dir := filepath.Dir(path)

	// see if the updater is available on PATH
	updater, err := exec.LookPath("nupdate")

	fmt.Println("UPDATER?", updater)
	if err != nil {

		fmt.Println("UPDATER NOT FOUND!")

		tmpFile := filepath.Join(config.TmpDir, "updater")

		// create a tmp updater in tmp dir
		f, err := os.Create(tmpFile)
		if err != nil {
			return err
		}
		defer f.Close()

		// the updateder is not available and needs to be downloaded
		dl := fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/update", config.OS, config.ARCH)

		fmt.Printf("Updater not found. Downloading from %s\n", dl)

		fileutil.Progress(dl, f)

		// ensure new CLI download matches the remote md5; if the download fails for any
		// reason this md5 should NOT match.
		md5 := fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/udpate", config.OS, config.ARCH)
		if _, err = util.MD5sMatch(tmpFile, md5); err != nil {
			fmt.Println("BONK!")
		}

		// make new updater executable
		if err := os.Chmod(tmpFile, 0755); err != nil {
			return err
		}

		// move updater to the same location as the cli
		if err = os.Rename(tmpFile, dir); err != nil {
			return err
		}
	}

	fmt.Println("RUNNING UPDATER!")

	// run the updater
	if err := exec.Command(updater, "-o", filepath.Base(path)).Run(); err != nil {
		Config.Fatal("[commands/update] exec.Command().Run() failed", err.Error())
	}

	// update the .update file
	return touchUpdate()
}

// touchUpdate updates the mod time on the ~/.nanobox/.update file
func touchUpdate() error {
	return os.Chtimes(config.UpdateFile, time.Now(), time.Now())
}
<commit_msg>nanobox update working; downloads updater if not available, and executes it (subsequently downloading the cli). need to figure out one issue with path<commit_after>//
package commands

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"time"

	"github.com/kardianos/osext"
	"github.com/nanobox-io/nanobox-golang-stylish"
	"github.com/spf13/cobra"

	"github.com/nanobox-io/nanobox/config"
	"github.com/nanobox-io/nanobox/util"
	fileutil "github.com/nanobox-io/nanobox/util/file"
	printutil "github.com/nanobox-io/nanobox/util/print"
)

var updateCmd = &cobra.Command{
	Use:   "update",
	Short: "Updates the CLI to the newest available version",
	Long:  ``,

	Run: update,
}

// update
func update(ccmd *cobra.Command, args []string) {

	update, err := updatable()
	if err != nil {
		Config.Error("Unable to determing if updates are available", err.Error())
		return
	}

	// if the md5s don't match or it's been forced, update
	switch {
	case update, config.Force:
		if err := runUpdate(); err != nil {
			fmt.Printf("ERR?? %#v\n", err.Error())
			if _, ok := err.(*os.LinkError); ok {
				fmt.Println(`Nanobox was unable to update, try again with admin privilege (ex. "sudo nanobox update")`)
			} else {
				Config.Fatal("[commands/update] runUpdate() failed", err.Error())
			}
		}
	default:
		fmt.Printf(stylish.SubBullet("[√] Nanobox is up-to-date"))
	}
}

// Update
func Update() error {

	update, err := updatable()
	if err != nil {
		Config.Error("Unable to determine if updates are available.", err.Error())
		return err
	}

	// stat the update file to get ModTime(); an error here means the file doesn't
	// exist. This is highly unlikely as the file is created if it doesn't exist
	// each time the CLI is run.
	fi, _ := os.Stat(config.UpdateFile)

	// if the md5s don't match and it's 'time' for an update (14 days), OR a force
	// update is issued, update
	if update && time.Since(fi.ModTime()).Hours() >= 336.0 {

		//
		switch printutil.Prompt("Nanobox is out of date, would you like to update it now (y/N)? ") {

		// don't update by default, assuming they'll just do it manually, prompting
		// again after 14 days
		default:
			fmt.Println("You can manually update at any time with 'nanobox update'.")
			return touchUpdate()

		// if yes continue to update
		case "Yes", "yes", "Y", "y":
			if err := runUpdate(); err != nil {
				if _, ok := err.(*os.LinkError); ok {
					fmt.Println(`Nanobox was unable to update, try again with admin privilege (ex. "sudo nanobox update")`)
				} else {
					Config.Fatal("[commands/update] runUpdate() failed", err.Error())
				}
			}
		}
	}

	return nil
}

// updatable
func updatable() (bool, error) {

	//
	path, err := osext.Executable()
	if err != nil {
		config.Log.Fatal("[commands/update] osext.Executable() failed", err.Error())
	}

	// check the md5 of the current executing cli against the remote md5;
	// os.Args[0] is used as the final interpolation to determine standard/dev versions
	match, err := Util.MD5sMatch(path, fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%s/%s/%s.md5", config.OS, config.ARCH, filepath.Base(os.Args[0])))
	if err != nil {
		return false, err
	}

	return !match, nil
}

// runUpdate attemtps to update using the updater; if it's not available nanobox
// will download it and then run it.
func runUpdate() error {

	//
	epath, err := osext.Executable()
	if err != nil {
		return err
	}

	// get the directory of the current executing cli
	dir := filepath.Dir(epath)

	// see if the updater is available on PATH
	upath, err := exec.LookPath("nanobox-update")
	if err != nil {

		tmpFile := filepath.Join(config.TmpDir, "nanobox-update")

		// create a tmp updater in tmp dir
		f, err := os.Create(tmpFile)
		if err != nil {
			return err
		}
		defer f.Close()

		// the updateder is not available and needs to be downloaded
		dl := fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/updaters/%s/%s/nanobox-update", config.OS, config.ARCH)

		fmt.Printf("Updater not found. Downloading from %s\n", dl)

		fileutil.Progress(dl, f)

		// ensure updater download matches the remote md5; if the download fails for any
		// reason this md5 should NOT match.
		md5 := fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/updaters/%s/%s/nanobox-udpate.md5", config.OS, config.ARCH)
		if _, err = util.MD5sMatch(tmpFile, md5); err != nil {
			return err
		}

		// make new updater executable
		if err := os.Chmod(tmpFile, 0755); err != nil {
			return err
		}

		// move updater to the same location as the cli
		if err = os.Rename(tmpFile, filepath.Join(dir, "nanobox-update")); err != nil {
			return err
		}
	}

	fmt.Println("RUNNING UPDATER!", upath, "|", epath, filepath.Base(epath))
	fmt.Println("PLACE!", filepath.Join(dir, "nanobox-update"))

	cmd := exec.Command(filepath.Join(dir, "nanobox-update"), "-o", filepath.Base(epath))

	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	// run the updater
	if err := cmd.Run(); err != nil {
		Config.Fatal("[commands/update] exec.Command().Run() failed", err.Error())
	}

	// update the .update file
	return touchUpdate()
}

// touchUpdate updates the mod time on the ~/.nanobox/.update file
func touchUpdate() error {
	return os.Chtimes(config.UpdateFile, time.Now(), time.Now())
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"testing"
	"time"

	"fancyirc/ircserver"
	"fancyirc/raft_logstore"
	"fancyirc/types"

	"github.com/hashicorp/raft"
)

func appendLog(logs []*raft.Log, msg string) []*raft.Log {
	return append(logs, &raft.Log{
		Type:  raft.LogCommand,
		Index: uint64(len(logs)),
		Data:  []byte(msg),
	})
}

func verifyEndState(t *testing.T) {
	s, ok := ircserver.GetSession(types.FancyId{Id: 1})
	if !ok {
		t.Fatalf("No session found after applying log messages")
	}
	if s.Nick != "secure_" {
		t.Fatalf("session.Nick: got %q, want %q", s.Nick, "secure_")
	}

	want := make(map[string]bool)
	want["#chaos-hd"] = true

	if !reflect.DeepEqual(s.Channels, want) {
		t.Fatalf("session.Channels: got %v, want %v", s.Channels, want)
	}
}

func TestCompaction(t *testing.T) {
	tempdir, err := ioutil.TempDir("", "fancy-test-")
	if err != nil {
		t.Fatalf("ioutil.TempDir: %v", err)
	}
	defer os.RemoveAll(tempdir)

	store, err := raft_logstore.NewFancyLogStore(tempdir)
	if err != nil {
		t.Fatalf("Unexpected error in NewFancyLogStore: %v", err)
	}
	fsm := FSM{store}

	var logs []*raft.Log
	logs = appendLog(logs, `{"Id": {"Id": 1}, "Type": 0, "Data": "auth"}`)
	logs = appendLog(logs, `{"Id": {"Id": 2}, "Session": {"Id": 1}, "Type": 2, "Data": "NICK sECuRE"}`)
	logs = appendLog(logs, `{"Id": {"Id": 3}, "Session": {"Id": 1}, "Type": 2, "Data": "NICK secure_"}`)
	logs = appendLog(logs, `{"Id": {"Id": 4}, "Session": {"Id": 1}, "Type": 2, "Data": "JOIN #chaos-hd"}`)
	logs = appendLog(logs, `{"Id": {"Id": 5}, "Session": {"Id": 1}, "Type": 2, "Data": "JOIN #i3"}`)
	logs = appendLog(logs, `{"Id": {"Id": 6}, "Session": {"Id": 1}, "Type": 2, "Data": "PRIVMSG #chaos-hd :heya"}`)
	logs = appendLog(logs, `{"Id": {"Id": 7}, "Session": {"Id": 1}, "Type": 2, "Data": "PRIVMSG #chaos-hd :newer message"}`)
	logs = appendLog(logs, `{"Id": {"Id": 8}, "Session": {"Id": 1}, "Type": 2, "Data": "PART #i3"}`)

	// These messages are too new to be compacted.
	nowId := time.Now().UnixNano()
	logs = appendLog(logs, `{"Id": {"Id": `+strconv.FormatInt(nowId, 10)+`}, "Session": {"Id": 1}, "Type": 2, "Data": "PART #chaos-hd"}`)
	nowId += 1
	logs = appendLog(logs, `{"Id": {"Id": `+strconv.FormatInt(nowId, 10)+`}, "Session": {"Id": 1}, "Type": 2, "Data": "JOIN #chaos-hd"}`)

	if err := store.StoreLogs(logs); err != nil {
		t.Fatalf("Unexpected error in store.StoreLogs: %v", err)
	}
	for _, log := range logs {
		fsm.Apply(log)
	}

	verifyEndState(t)

	snapshot, err := fsm.Snapshot()
	if err != nil {
		t.Fatalf("Unexpected error in fsm.Snapshot(): %v", err)
	}

	fancysnap, ok := snapshot.(*fancySnapshot)
	if !ok {
		t.Fatalf("fsm.Snapshot() return value is not a fancySnapshot")
	}
	if fancysnap.indexes[len(fancysnap.indexes)-1] != uint64(len(logs)-1) ||
		fancysnap.indexes[len(fancysnap.indexes)-2] != uint64(len(logs)-2) {
		t.Fatalf("snapshot does not retain the last two (recent) messages")
	}

	fss, err := raft.NewFileSnapshotStore(tempdir, 5, nil)
	if err != nil {
		t.Fatalf("%v", err)
	}

	sink, err := fss.Create(uint64(len(logs)), 1, []byte{})
	if err != nil {
		t.Fatalf("fss.Create: %v", err)
	}

	if err := snapshot.Persist(sink); err != nil {
		t.Fatalf("Unexpected error in snapshot.Persist(): %v", err)
	}

	snapshots, err := fss.List()
	if err != nil {
		t.Fatalf("fss.List(): %v", err)
	}
	if len(snapshots) != 1 {
		t.Fatalf("len(snapshots): got %d, want 1", len(snapshots))
	}
	_, readcloser, err := fss.Open(snapshots[0].ID)
	if err != nil {
		t.Fatalf("fss.Open(%s): %v", snapshots[0].ID, err)
	}

	if err := fsm.Restore(readcloser); err != nil {
		t.Fatalf("fsm.Restore(): %v", err)
	}

	indexes, err := store.GetAll()
	if err != nil {
		t.Fatalf("store.GetAll(): %v", err)
	}

	if len(indexes) >= len(logs) {
		t.Fatalf("Compaction did not decrease log size. got: %d, want: < %d", len(indexes), len(logs))
	}

	verifyEndState(t)
}
<commit_msg>fix compaction test: need to clear state first<commit_after>package main

import (
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"testing"
	"time"

	"fancyirc/ircserver"
	"fancyirc/raft_logstore"
	"fancyirc/types"

	"github.com/hashicorp/raft"
)

func appendLog(logs []*raft.Log, msg string) []*raft.Log {
	return append(logs, &raft.Log{
		Type:  raft.LogCommand,
		Index: uint64(len(logs)),
		Data:  []byte(msg),
	})
}

func verifyEndState(t *testing.T) {
	s, ok := ircserver.GetSession(types.FancyId{Id: 1})
	if !ok {
		t.Fatalf("No session found after applying log messages")
	}
	if s.Nick != "secure_" {
		t.Fatalf("session.Nick: got %q, want %q", s.Nick, "secure_")
	}

	want := make(map[string]bool)
	want["#chaos-hd"] = true

	if !reflect.DeepEqual(s.Channels, want) {
		t.Fatalf("session.Channels: got %v, want %v", s.Channels, want)
	}
}

func TestCompaction(t *testing.T) {
	ircserver.ClearState()

	tempdir, err := ioutil.TempDir("", "fancy-test-")
	if err != nil {
		t.Fatalf("ioutil.TempDir: %v", err)
	}
	defer os.RemoveAll(tempdir)

	store, err := raft_logstore.NewFancyLogStore(tempdir)
	if err != nil {
		t.Fatalf("Unexpected error in NewFancyLogStore: %v", err)
	}
	fsm := FSM{store}

	var logs []*raft.Log
	logs = appendLog(logs, `{"Id": {"Id": 1}, "Type": 0, "Data": "auth"}`)
	logs = appendLog(logs, `{"Id": {"Id": 2}, "Session": {"Id": 1}, "Type": 2, "Data": "NICK sECuRE"}`)
	logs = appendLog(logs, `{"Id": {"Id": 3}, "Session": {"Id": 1}, "Type": 2, "Data": "NICK secure_"}`)
	logs = appendLog(logs, `{"Id": {"Id": 4}, "Session": {"Id": 1}, "Type": 2, "Data": "JOIN #chaos-hd"}`)
	logs = appendLog(logs, `{"Id": {"Id": 5}, "Session": {"Id": 1}, "Type": 2, "Data": "JOIN #i3"}`)
	logs = appendLog(logs, `{"Id": {"Id": 6}, "Session": {"Id": 1}, "Type": 2, "Data": "PRIVMSG #chaos-hd :heya"}`)
	logs = appendLog(logs, `{"Id": {"Id": 7}, "Session": {"Id": 1}, "Type": 2, "Data": "PRIVMSG #chaos-hd :newer message"}`)
	logs = appendLog(logs, `{"Id": {"Id": 8}, "Session": {"Id": 1}, "Type": 2, "Data": "PART #i3"}`)

	// These messages are too new to be compacted.
	nowId := time.Now().UnixNano()
	logs = appendLog(logs, `{"Id": {"Id": `+strconv.FormatInt(nowId, 10)+`}, "Session": {"Id": 1}, "Type": 2, "Data": "PART #chaos-hd"}`)
	nowId += 1
	logs = appendLog(logs, `{"Id": {"Id": `+strconv.FormatInt(nowId, 10)+`}, "Session": {"Id": 1}, "Type": 2, "Data": "JOIN #chaos-hd"}`)

	if err := store.StoreLogs(logs); err != nil {
		t.Fatalf("Unexpected error in store.StoreLogs: %v", err)
	}
	for _, log := range logs {
		fsm.Apply(log)
	}

	verifyEndState(t)

	snapshot, err := fsm.Snapshot()
	if err != nil {
		t.Fatalf("Unexpected error in fsm.Snapshot(): %v", err)
	}

	fancysnap, ok := snapshot.(*fancySnapshot)
	if !ok {
		t.Fatalf("fsm.Snapshot() return value is not a fancySnapshot")
	}
	if fancysnap.indexes[len(fancysnap.indexes)-1] != uint64(len(logs)-1) ||
		fancysnap.indexes[len(fancysnap.indexes)-2] != uint64(len(logs)-2) {
		t.Fatalf("snapshot does not retain the last two (recent) messages")
	}

	fss, err := raft.NewFileSnapshotStore(tempdir, 5, nil)
	if err != nil {
		t.Fatalf("%v", err)
	}

	sink, err := fss.Create(uint64(len(logs)), 1, []byte{})
	if err != nil {
		t.Fatalf("fss.Create: %v", err)
	}

	if err := snapshot.Persist(sink); err != nil {
		t.Fatalf("Unexpected error in snapshot.Persist(): %v", err)
	}

	snapshots, err := fss.List()
	if err != nil {
		t.Fatalf("fss.List(): %v", err)
	}
	if len(snapshots) != 1 {
		t.Fatalf("len(snapshots): got %d, want 1", len(snapshots))
	}
	_, readcloser, err := fss.Open(snapshots[0].ID)
	if err != nil {
		t.Fatalf("fss.Open(%s): %v", snapshots[0].ID, err)
	}

	if err := fsm.Restore(readcloser); err != nil {
		t.Fatalf("fsm.Restore(): %v", err)
	}

	indexes, err := store.GetAll()
	if err != nil {
		t.Fatalf("store.GetAll(): %v", err)
	}

	if len(indexes) >= len(logs) {
		t.Fatalf("Compaction did not decrease log size. got: %d, want: < %d", len(indexes), len(logs))
	}

	verifyEndState(t)
}
<|endoftext|>


<commit_before>// Copyright 2019 The LUCI Authors.
//
// 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 (
	"flag"
	"reflect"
	"strings"

	"github.com/golang/protobuf/proto"
	"google.golang.org/genproto/protobuf/field_mask"

	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
)

type buildFieldFlags struct {
	all              bool
	inputProperties  bool
	outputProperties bool
	steps            bool
}

func (f *buildFieldFlags) Register(fs *flag.FlagSet) {
	fs.BoolVar(&f.all, "A", false, "Print build entirely")
	fs.BoolVar(&f.steps, "steps", false, "Print steps")
	fs.BoolVar(&f.inputProperties, "ip", false, "Print input properties")
	fs.BoolVar(&f.outputProperties, "op", false, "Print output properties")
}

func (f *buildFieldFlags) FieldMask() *field_mask.FieldMask {
	if f.all {
		ret := &field_mask.FieldMask{}
		for _, p := range proto.GetProperties(reflect.TypeOf(buildbucketpb.Build{})).Prop {
			if !strings.HasPrefix(p.OrigName, "XXX") {
				ret.Paths = append(ret.Paths, p.OrigName)
			}
		}
		return ret
	}

	ret := &field_mask.FieldMask{
		Paths: []string{
			"builder",
			"create_time",
			"created_by",
			"end_time",
			"id",
			"input.experimental",
			"input.gerrit_changes",
			"input.gitiles_commit",
			"number",
			"start_time",
			"status",
			"status_details",
			"summary_markdown",
			"tags",
			"update_time",
		},
	}

	if f.inputProperties {
		ret.Paths = append(ret.Paths, "input.properties")
	}

	if f.outputProperties {
		ret.Paths = append(ret.Paths, "output.properties")
	}

	if f.steps {
		ret.Paths = append(ret.Paths, "steps")
	}

	return ret
}
<commit_msg>[buildbucket] Merge -ip and -op into -p<commit_after>// Copyright 2019 The LUCI Authors.
//
// 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 (
	"flag"
	"reflect"
	"strings"

	"github.com/golang/protobuf/proto"
	"google.golang.org/genproto/protobuf/field_mask"

	buildbucketpb "go.chromium.org/luci/buildbucket/proto"
)

type buildFieldFlags struct {
	all        bool
	properties bool
	steps      bool
}

func (f *buildFieldFlags) Register(fs *flag.FlagSet) {
	fs.BoolVar(&f.all, "A", false, "Print build entirely")
	fs.BoolVar(&f.steps, "steps", false, "Print steps")
	fs.BoolVar(&f.properties, "p", false, "Print input/output properties")
}

func (f *buildFieldFlags) FieldMask() *field_mask.FieldMask {
	if f.all {
		ret := &field_mask.FieldMask{}
		for _, p := range proto.GetProperties(reflect.TypeOf(buildbucketpb.Build{})).Prop {
			if !strings.HasPrefix(p.OrigName, "XXX") {
				ret.Paths = append(ret.Paths, p.OrigName)
			}
		}
		return ret
	}

	ret := &field_mask.FieldMask{
		Paths: []string{
			"builder",
			"create_time",
			"created_by",
			"end_time",
			"id",
			"input.experimental",
			"input.gerrit_changes",
			"input.gitiles_commit",
			"number",
			"start_time",
			"status",
			"status_details",
			"summary_markdown",
			"tags",
			"update_time",
		},
	}

	if f.properties {
		ret.Paths = append(ret.Paths, "input.properties", "output.properties")
	}

	if f.steps {
		ret.Paths = append(ret.Paths, "steps")
	}

	return ret
}
<|endoftext|>


<commit_before>package chain

import (
	mchain "github.com/btcboost/copernicus/model/chain"
	"github.com/btcboost/copernicus/model/blockindex"
	"github.com/btcboost/copernicus/util"
)

func LocateBlocks(bl *mchain.BlockLocator, endHash *util.Hash,maxLength int) error {
	return nil
}

func LocateHeaders(bl *mchain.BlockLocator, endHash *util.Hash,maxLength int) error {

	return nil
}

func FindForkInGlobalIndex(chain *mchain.Chain, locator *mchain.BlockLocator) *blockindex.BlockIndex {
	gChain := mchain.GetInstance()
	// Find the first block the caller has in the main chain
	for _, hash := range locator.GetBlockHashList() {
		bi := gChain.FindBlockIndex(hash)
		if bi != nil {
			if chain.Contains(bi) {
				return bi
			}
			if bi.GetAncestor(chain.Height()) == chain.Tip() {
				return chain.Tip()
			}
		}
	}
	return chain.Genesis()
}

<commit_msg>add locator logic<commit_after>package chain

import (
	"github.com/btcboost/copernicus/model/block"
	mchain "github.com/btcboost/copernicus/model/chain"
	"github.com/btcboost/copernicus/model/blockindex"
	"github.com/btcboost/copernicus/persist/global"
	"github.com/btcboost/copernicus/util"
)

const (
	MaxHeadersResults = 2000
	MaxBlocksResults = 500

)
func LocateBlocks(locator *mchain.BlockLocator, endHash *util.Hash) []util.Hash {
	global.CsMain.Lock()
	defer global.CsMain.Unlock()
	var bi *blockindex.BlockIndex
	gChain := mchain.GetInstance()
	ret := make([]util.Hash, 0)

	bi = FindForkInGlobalIndex(gChain, locator)
	if bi != nil{
		bi =  gChain.Next(bi)
	}

	nLimits := MaxBlocksResults
	for{
		if bi == nil&& nLimits <= 0 || bi.GetBlockHash().IsEqual(endHash){
			break
		}
		bh := bi.GetBlockHeader()
		ret = append(ret, bh.GetHash())
		nLimits -= 1
		bi=gChain.Next(bi)
	}
	return ret
}

func LocateHeaders(locator *mchain.BlockLocator, endHash *util.Hash) []block.BlockHeader {
	global.CsMain.Lock()
	defer global.CsMain.Unlock()
	var bi *blockindex.BlockIndex
	gChain := mchain.GetInstance()
	ret := make([]block.BlockHeader, 0)
	if locator.IsNull(){
		bi = gChain.FindBlockIndex(*endHash)
		if bi == nil{
			return ret
		}
	}else{
		bi = FindForkInGlobalIndex(gChain, locator)
		if bi != nil{
			bi =  gChain.Next(bi)
		}
	}
	nLimits := MaxHeadersResults
	for{
		if bi==nil && nLimits <= 0 || bi.GetBlockHash().IsEqual(endHash){
			break
		}
		bh := bi.GetBlockHeader()
		ret = append(ret, *bh)
		nLimits -= 1
		bi=gChain.Next(bi)
	}
	return ret
}

func FindForkInGlobalIndex(chain *mchain.Chain, locator *mchain.BlockLocator) *blockindex.BlockIndex {
	gChain := mchain.GetInstance()
	// Find the first block the caller has in the main chain
	for _, hash := range locator.GetBlockHashList() {
		bi := gChain.FindBlockIndex(hash)
		if bi != nil {
			if chain.Contains(bi) {
				return bi
			}
			if bi.GetAncestor(chain.Height()) == chain.Tip() {
				return chain.Tip()
			}
		}
	}
	return chain.Genesis()
}

<|endoftext|>


<commit_before>package lord_test

import (
	"time"

	. "github.com/onsi/gomega"

	. "github.com/onsi/ginkgo/extensions/table"

	"github.com/concourse/time-resource/lord"
	"github.com/concourse/time-resource/models"
)

type testCase struct {
	interval string

	location string

	start string
	stop  string

	days []time.Weekday

	prev    string
	prevDay time.Weekday

	now       string
	extraTime time.Duration
	nowDay    time.Weekday

	result bool
}

const exampleFormatWithTZ = "3:04 PM -0700"
const exampleFormatWithoutTZ = "3:04 PM"

func (tc testCase) Run() {
	var tl lord.TimeLord

	if tc.location != "" {
		loc, err := time.LoadLocation(tc.location)
		Expect(err).NotTo(HaveOccurred())

		tl.Location = (*models.Location)(loc)
	}

	var format string
	if tl.Location != nil {
		format = exampleFormatWithoutTZ
	} else {
		format = exampleFormatWithTZ
	}

	if tc.start != "" {
		startTime, err := time.Parse(format, tc.start)
		Expect(err).NotTo(HaveOccurred())

		start := models.NewTimeOfDay(startTime.UTC())
		tl.Start = &start
	}

	if tc.stop != "" {
		stopTime, err := time.Parse(format, tc.stop)
		Expect(err).NotTo(HaveOccurred())

		stop := models.NewTimeOfDay(stopTime.UTC())
		tl.Stop = &stop
	}

	if tc.interval != "" {
		interval, err := time.ParseDuration(tc.interval)
		Expect(err).NotTo(HaveOccurred())

		tl.Interval = (*models.Interval)(&interval)
	}

	tl.Days = make([]models.Weekday, len(tc.days))
	for i, d := range tc.days {
		tl.Days[i] = models.Weekday(d)
	}

	now, err := time.Parse(exampleFormatWithTZ, tc.now)
	Expect(err).NotTo(HaveOccurred())

	for now.Weekday() != tc.nowDay {
		now = now.AddDate(0, 0, 1)
	}

	if tc.prev != "" {
		prev, err := time.Parse(exampleFormatWithTZ, tc.prev)
		Expect(err).NotTo(HaveOccurred())

		for prev.Weekday() != tc.prevDay {
			prev = prev.AddDate(0, 0, 1)
		}

		tl.PreviousTime = prev
	}

	result := tl.Check(now.UTC())
	Expect(result).To(Equal(tc.result))
}

var _ = DescribeTable("A range without a previous time", (testCase).Run,
	Entry("between the start and stop time", testCase{
		start:  "2:00 AM +0000",
		stop:   "4:00 AM +0000",
		now:    "3:00 AM +0000",
		result: true,
	}),
	Entry("between the start and stop time down to the minute", testCase{
		start:  "2:01 AM +0000",
		stop:   "2:03 AM +0000",
		now:    "2:02 AM +0000",
		result: true,
	}),
	Entry("not between the start and stop time", testCase{
		start:  "2:00 AM +0000",
		stop:   "4:00 AM +0000",
		now:    "5:00 AM +0000",
		result: false,
	}),
	Entry("after the stop time, down to the minute", testCase{
		start:  "2:00 AM +0000",
		stop:   "4:00 AM +0000",
		now:    "4:10 AM +0000",
		result: false,
	}),
	Entry("before the start time, down to the minute", testCase{
		start:  "11:07 AM +0000",
		stop:   "11:10 AM +0000",
		now:    "11:05 AM +0000",
		result: false,
	}),
	Entry("one nanosecond before the start time", testCase{
		start:     "3:04 AM +0000",
		stop:      "3:07 AM +0000",
		now:       "3:03 AM +0000",
		extraTime: time.Minute - time.Nanosecond,
		result:    false,
	}),
	Entry("equal to the start time", testCase{
		start:  "3:04 AM +0000",
		stop:   "3:07 AM +0000",
		now:    "3:04 AM +0000",
		result: true,
	}),
	Entry("one nanosecond before the stop time", testCase{
		start:     "3:04 AM +0000",
		stop:      "3:07 AM +0000",
		now:       "3:06 AM +0000",
		extraTime: time.Minute - time.Nanosecond,
		result:    true,
	}),
	Entry("equal to the stop time", testCase{
		start:  "3:04 AM +0000",
		stop:   "3:07 AM +0000",
		now:    "3:07 AM +0000",
		result: false,
	}),

	Entry("between the start and stop time but the stop time is before the start time, spanning more than a day", testCase{
		start:  "5:00 AM +0000",
		stop:   "1:00 AM +0000",
		now:    "6:00 AM +0000",
		result: true,
	}),
	Entry("between the start and stop time but the stop time is before the start time, spanning half a day", testCase{
		start:  "8:00 PM +0000",
		stop:   "8:00 AM +0000",
		now:    "1:00 AM +0000",
		result: true,
	}),

	Entry("between the start and stop time but the compare time is in a different timezone", testCase{
		start:  "2:00 AM -0600",
		stop:   "6:00 AM -0600",
		now:    "1:00 AM -0700",
		result: true,
	}),

	Entry("covering almost a full day", testCase{
		start:  "12:01 AM -0700",
		stop:   "11:59 PM -0700",
		now:    "1:10 AM +0000",
		result: true,
	}),
)

var _ = DescribeTable("A range with a location and no previous time", (testCase).Run,
	Entry("between the start and stop time in a given location", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		now:      "6:00 PM +0000",
		result:   true,
	}),
	Entry("between the start and stop time in a given location on a matching day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		days:     []time.Weekday{time.Wednesday},
		now:      "6:00 PM +0000",
		nowDay:   time.Wednesday,
		result:   true,
	}),
	Entry("not between the start and stop time in a given location", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		now:      "8:00 PM +0000",
		result:   false,
	}),
	Entry("between the start and stop time in a given location but not on a matching day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		days:     []time.Weekday{time.Wednesday},
		now:      "6:00 PM +0000",
		nowDay:   time.Thursday,
		result:   false,
	}),
	Entry("between the start and stop time in a given location and on a matching day compared to UTC", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "10:00 PM",
		stop:     "11:00 PM",
		days:     []time.Weekday{time.Wednesday},
		now:      "2:00 AM +0000",
		nowDay:   time.Thursday,
		result:   true,
	}),
)

var _ = DescribeTable("A range with a location and a previous time", (testCase).Run,
	Entry("between the start and stop time in a given location, on a new day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",

		prev:    "6:00 PM +0000",
		prevDay: time.Wednesday,
		now:     "6:00 PM +0000",
		nowDay:  time.Thursday,

		result: true,
	}),
	Entry("not between the start and stop time in a given location, on the same day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",

		prev:    "6:00 PM +0000",
		prevDay: time.Wednesday,
		now:     "6:01 PM +0000",
		nowDay:  time.Wednesday,

		result: false,
	}),
)

var _ = DescribeTable("An interval", (testCase).Run,
	Entry("without a previous time", testCase{
		interval: "2m",
		now:      "12:00 PM +0000",
		result:   true,
	}),
	Entry("with a previous time that has not elapsed", testCase{
		interval: "2m",
		prev:     "12:00 PM +0000",
		now:      "12:01 PM +0000",
		result:   false,
	}),
	Entry("with a previous time that has elapsed", testCase{
		interval: "2m",
		prev:     "12:00 PM +0000",
		now:      "12:02 PM +0000",
		result:   true,
	}),
)

var _ = DescribeTable("A range with an interval and a previous time", (testCase).Run,
	Entry("between the start and stop time, on a new day", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev:    "2:58 PM +0000",
		prevDay: time.Wednesday,
		now:     "1:00 PM +0000",
		nowDay:  time.Thursday,

		result: true,
	}),
	Entry("between the start and stop time, elapsed", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev: "1:02 PM +0000",
		now:  "1:04 PM +0000",

		result: true,
	}),
	Entry("between the start and stop time, not elapsed", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev: "1:02 PM +0000",
		now:  "1:03 PM +0000",

		result: false,
	}),
	Entry("not between the start and stop time, elapsed", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev: "2:58 PM +0000",
		now:  "3:02 PM +0000",

		result: false,
	}),
)
<commit_msg>widen time range to account for DST<commit_after>package lord_test

import (
	"time"

	. "github.com/onsi/gomega"

	. "github.com/onsi/ginkgo/extensions/table"

	"github.com/concourse/time-resource/lord"
	"github.com/concourse/time-resource/models"
)

type testCase struct {
	interval string

	location string

	start string
	stop  string

	days []time.Weekday

	prev    string
	prevDay time.Weekday

	now       string
	extraTime time.Duration
	nowDay    time.Weekday

	result bool
}

const exampleFormatWithTZ = "3:04 PM -0700"
const exampleFormatWithoutTZ = "3:04 PM"

func (tc testCase) Run() {
	var tl lord.TimeLord

	if tc.location != "" {
		loc, err := time.LoadLocation(tc.location)
		Expect(err).NotTo(HaveOccurred())

		tl.Location = (*models.Location)(loc)
	}

	var format string
	if tl.Location != nil {
		format = exampleFormatWithoutTZ
	} else {
		format = exampleFormatWithTZ
	}

	if tc.start != "" {
		startTime, err := time.Parse(format, tc.start)
		Expect(err).NotTo(HaveOccurred())

		start := models.NewTimeOfDay(startTime.UTC())
		tl.Start = &start
	}

	if tc.stop != "" {
		stopTime, err := time.Parse(format, tc.stop)
		Expect(err).NotTo(HaveOccurred())

		stop := models.NewTimeOfDay(stopTime.UTC())
		tl.Stop = &stop
	}

	if tc.interval != "" {
		interval, err := time.ParseDuration(tc.interval)
		Expect(err).NotTo(HaveOccurred())

		tl.Interval = (*models.Interval)(&interval)
	}

	tl.Days = make([]models.Weekday, len(tc.days))
	for i, d := range tc.days {
		tl.Days[i] = models.Weekday(d)
	}

	now, err := time.Parse(exampleFormatWithTZ, tc.now)
	Expect(err).NotTo(HaveOccurred())

	for now.Weekday() != tc.nowDay {
		now = now.AddDate(0, 0, 1)
	}

	if tc.prev != "" {
		prev, err := time.Parse(exampleFormatWithTZ, tc.prev)
		Expect(err).NotTo(HaveOccurred())

		for prev.Weekday() != tc.prevDay {
			prev = prev.AddDate(0, 0, 1)
		}

		tl.PreviousTime = prev
	}

	result := tl.Check(now.UTC())
	Expect(result).To(Equal(tc.result))
}

var _ = DescribeTable("A range without a previous time", (testCase).Run,
	Entry("between the start and stop time", testCase{
		start:  "2:00 AM +0000",
		stop:   "4:00 AM +0000",
		now:    "3:00 AM +0000",
		result: true,
	}),
	Entry("between the start and stop time down to the minute", testCase{
		start:  "2:01 AM +0000",
		stop:   "2:03 AM +0000",
		now:    "2:02 AM +0000",
		result: true,
	}),
	Entry("not between the start and stop time", testCase{
		start:  "2:00 AM +0000",
		stop:   "4:00 AM +0000",
		now:    "5:00 AM +0000",
		result: false,
	}),
	Entry("after the stop time, down to the minute", testCase{
		start:  "2:00 AM +0000",
		stop:   "4:00 AM +0000",
		now:    "4:10 AM +0000",
		result: false,
	}),
	Entry("before the start time, down to the minute", testCase{
		start:  "11:07 AM +0000",
		stop:   "11:10 AM +0000",
		now:    "11:05 AM +0000",
		result: false,
	}),
	Entry("one nanosecond before the start time", testCase{
		start:     "3:04 AM +0000",
		stop:      "3:07 AM +0000",
		now:       "3:03 AM +0000",
		extraTime: time.Minute - time.Nanosecond,
		result:    false,
	}),
	Entry("equal to the start time", testCase{
		start:  "3:04 AM +0000",
		stop:   "3:07 AM +0000",
		now:    "3:04 AM +0000",
		result: true,
	}),
	Entry("one nanosecond before the stop time", testCase{
		start:     "3:04 AM +0000",
		stop:      "3:07 AM +0000",
		now:       "3:06 AM +0000",
		extraTime: time.Minute - time.Nanosecond,
		result:    true,
	}),
	Entry("equal to the stop time", testCase{
		start:  "3:04 AM +0000",
		stop:   "3:07 AM +0000",
		now:    "3:07 AM +0000",
		result: false,
	}),

	Entry("between the start and stop time but the stop time is before the start time, spanning more than a day", testCase{
		start:  "5:00 AM +0000",
		stop:   "1:00 AM +0000",
		now:    "6:00 AM +0000",
		result: true,
	}),
	Entry("between the start and stop time but the stop time is before the start time, spanning half a day", testCase{
		start:  "8:00 PM +0000",
		stop:   "8:00 AM +0000",
		now:    "1:00 AM +0000",
		result: true,
	}),

	Entry("between the start and stop time but the compare time is in a different timezone", testCase{
		start:  "2:00 AM -0600",
		stop:   "6:00 AM -0600",
		now:    "1:00 AM -0700",
		result: true,
	}),

	Entry("covering almost a full day", testCase{
		start:  "12:01 AM -0700",
		stop:   "11:59 PM -0700",
		now:    "1:10 AM +0000",
		result: true,
	}),
)

var _ = DescribeTable("A range with a location and no previous time", (testCase).Run,
	Entry("between the start and stop time in a given location", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		now:      "6:00 PM +0000",
		result:   true,
	}),
	Entry("between the start and stop time in a given location on a matching day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		days:     []time.Weekday{time.Wednesday},
		now:      "6:00 PM +0000",
		nowDay:   time.Wednesday,
		result:   true,
	}),
	Entry("not between the start and stop time in a given location", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		now:      "8:00 PM +0000",
		result:   false,
	}),
	Entry("between the start and stop time in a given location but not on a matching day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",
		days:     []time.Weekday{time.Wednesday},
		now:      "6:00 PM +0000",
		nowDay:   time.Thursday,
		result:   false,
	}),
	Entry("between the start and stop time in a given location and on a matching day compared to UTC", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "9:00 PM",
		stop:     "11:00 PM",
		days:     []time.Weekday{time.Wednesday},
		now:      "2:00 AM +0000",
		nowDay:   time.Thursday,
		result:   true,
	}),
)

var _ = DescribeTable("A range with a location and a previous time", (testCase).Run,
	Entry("between the start and stop time in a given location, on a new day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",

		prev:    "6:00 PM +0000",
		prevDay: time.Wednesday,
		now:     "6:00 PM +0000",
		nowDay:  time.Thursday,

		result: true,
	}),
	Entry("not between the start and stop time in a given location, on the same day", testCase{
		location: "America/Indiana/Indianapolis",
		start:    "1:00 PM",
		stop:     "3:00 PM",

		prev:    "6:00 PM +0000",
		prevDay: time.Wednesday,
		now:     "6:01 PM +0000",
		nowDay:  time.Wednesday,

		result: false,
	}),
)

var _ = DescribeTable("An interval", (testCase).Run,
	Entry("without a previous time", testCase{
		interval: "2m",
		now:      "12:00 PM +0000",
		result:   true,
	}),
	Entry("with a previous time that has not elapsed", testCase{
		interval: "2m",
		prev:     "12:00 PM +0000",
		now:      "12:01 PM +0000",
		result:   false,
	}),
	Entry("with a previous time that has elapsed", testCase{
		interval: "2m",
		prev:     "12:00 PM +0000",
		now:      "12:02 PM +0000",
		result:   true,
	}),
)

var _ = DescribeTable("A range with an interval and a previous time", (testCase).Run,
	Entry("between the start and stop time, on a new day", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev:    "2:58 PM +0000",
		prevDay: time.Wednesday,
		now:     "1:00 PM +0000",
		nowDay:  time.Thursday,

		result: true,
	}),
	Entry("between the start and stop time, elapsed", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev: "1:02 PM +0000",
		now:  "1:04 PM +0000",

		result: true,
	}),
	Entry("between the start and stop time, not elapsed", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev: "1:02 PM +0000",
		now:  "1:03 PM +0000",

		result: false,
	}),
	Entry("not between the start and stop time, elapsed", testCase{
		interval: "2m",

		start: "1:00 PM +0000",
		stop:  "3:00 PM +0000",

		prev: "2:58 PM +0000",
		now:  "3:02 PM +0000",

		result: false,
	}),
)
<|endoftext|>


<commit_before>package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"errors"
	"fmt"
	"github.com/ernado/selectel/storage"
	"github.com/jwaldrip/odin/cli"
	"github.com/olekukonko/tablewriter"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
)

const (
	envKey        = storage.EnvKey
	envUser       = storage.EnvUser
	version       = "1.1"
	cacheFilename = "~selct.cache~" + version
	envCache      = "SELECTEL_CACHE"
	envContainer  = "SELECTEL_CONTAINER"
)

var (
	client         = cli.New(version, "Selectel storage command line client", connect)
	user, key      string
	container      string
	api            storage.API
	debug          bool
	cache          bool
	cacheSecure    bool
	errorNotEnough = errors.New("Not enought arguments")
)

func encryptionKey() []byte {
	hasher := sha256.New()
	hasher.Write([]byte("selectel storage command line client"))
	hasher.Write([]byte(key))
	hasher.Write([]byte(user))
	return hasher.Sum(nil)
}

func encrypt(data []byte) []byte {
	block, err := aes.NewCipher(encryptionKey())
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, aes.BlockSize+len(data))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], data)
	return ciphertext
}

func decrypt(data []byte) ([]byte, error) {
	block, err := aes.NewCipher(encryptionKey())
	if err != nil {
		return nil, err
	}

	if len(data) < aes.BlockSize {
		return nil, errors.New("ciphertext too short")
	}
	iv := data[:aes.BlockSize]
	data = data[aes.BlockSize:]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(data, data)

	return data, nil
}

func init() {
	client.DefineBoolFlagVar(&debug, "debug", false, "debug mode")
	client.DefineBoolFlagVar(&cache, "cache", false, fmt.Sprintf("cache token in file (%s)", envCache))
	client.DefineBoolFlagVar(&cacheSecure, "cache.secure", true, "encrypt/decrypt token with user-key pair (true by default)")
	client.DefineStringFlag("key", "", fmt.Sprintf("selectel storage key (%s)", envKey))
	client.AliasFlag('k', "key")
	client.DefineStringFlag("user", "", fmt.Sprintf("selectel storage user (%s)", envUser))
	client.AliasFlag('u', "user")
	client.DefineStringFlag("container", "", fmt.Sprintf("default container (%s)", envContainer))
	client.AliasFlag('c', "container")

	infoCommand := client.DefineSubCommand("info", "print information about storage/container/object", wrap(info))
	infoCommand.DefineStringFlag("type", "storage", "storage, container or object")
	infoCommand.AliasFlag('t', "type")

	listCommand := client.DefineSubCommand("list", "list objects in container/storage", wrap(list))
	listCommand.DefineStringFlag("type", "storage", "storage or container")
	listCommand.AliasFlag('t', "type")

	client.DefineSubCommand("upload", "upload object to container", wrap(upload))
	downloadCommand := client.DefineSubCommand("download", "download object from container", wrap(download))
	downloadCommand.DefineStringFlag("path", "", "destination path")
	downloadCommand.AliasFlag('p', "path")

	client.DefineSubCommand("create", "create container", wrap(create))

	removeCommand := client.DefineSubCommand("remove", "remove object or container", wrap(remove))
	removeCommand.DefineStringFlag("type", "object", "container or object")
	removeCommand.DefineBoolFlag("force", false, "remove container with files")
	removeCommand.AliasFlag('f', "force")
	removeCommand.AliasFlag('t', "type")
}

func readFlag(c cli.Command, name, env string) string {
	if len(os.Getenv(env)) > 0 {
		return os.Getenv(env)
	}
	return c.Flag(name).String()
}

func blank(s string) bool {
	return len(s) == 0
}

func load() ([]byte, error) {
	f, err := os.Open(cacheFilename)
	if err != nil {
		return nil, err
	}
	data, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	if !cacheSecure {
		return data, nil
	}
	return decrypt(data)
}

// connect reads credentials and performs auth
func connect(c cli.Command) {
	var err error

	key = readFlag(c, "key", envKey)
	user = readFlag(c, "user", envUser)
	container = readFlag(c, "container", envContainer)

	if strings.ToLower(os.Getenv(envCache)) == "true" {
		cache = true
	}

	if cache {
		var data []byte
		data, err = load()
		if err != nil {
			log.Println(err)
		} else {
			api, err = storage.NewFromCache(data)
			if err == nil {
				return
			} else {
				log.Println("unable to load from cache:", err)
			}
		}
	} else {
		os.Remove(cacheFilename)
	}

	// checking for blank credentials
	if blank(key) || blank(user) && api != nil {
		log.Fatal(storage.ErrorBadCredentials)
	}

	// connencting to api
	api = storage.NewAsync(user, key)
	api.Debug(debug)
	if err = api.Auth(user, key); err != nil {
		log.Fatal(err)
	}
}

func wrap(callback func(cli.Command)) func(cli.Command) {
	return func(c cli.Command) {
		connect(c.Parent())
		defer func() {
			if !cache {
				return
			}
			data, _ := api.Dump()
			if cacheSecure {
				data = encrypt(data)
			}
			f, _ := os.Create(cacheFilename)
			f.Write(data)
		}()
		callback(c)
	}
}

// info prints information about storage
func info(c cli.Command) {
	var (
		containerName = container
		objectName    string
		data          interface{}
		err           error
		arglen        = len(c.Args())
		command       = c.Flag("type").String()
	)

	defer func() {
		if err != nil {
			log.Fatal(err)
		}
		if blank(containerName) || command == "storage" {
			data = api.Info()
		} else {
			containerApi := api.Container(containerName)
			if blank(objectName) {
				data, err = containerApi.Info()
			} else {
				data, err = containerApi.Object(objectName).Info()
			}
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%+v\n", data)
	}()

	if arglen > 0 {
		if command == "container" {
			containerName = c.Arg(0).String()
			return
		}
		command = "object"
		if !blank(containerName) && arglen == 1 {
			objectName = c.Arg(0).String()
			return
		}
		if arglen == 2 {
			containerName = c.Arg(0).String()
			objectName = c.Arg(1).String()
			return
		}
	}
	if command == "container" && !blank(containerName) {
		return
	}
	if command == "storage" {
		return
	}
	err = errorNotEnough
}

func remove(c cli.Command) {
	var (
		arglen  = len(c.Args())
		object  string
		err     error
		message string
		objects []storage.ObjectAPI
	)
	if arglen == 2 {
		container = c.Arg(0).String()
		object = c.Arg(1).String()
	}
	if arglen == 1 {
		if c.Flag("type").String() == "container" {
			container = c.Arg(0).String()
		} else {
			object = c.Arg(0).String()
		}
	}
	if blank(container) {
		log.Fatal(errorNotEnough)
	}
	if blank(object) {
		containerApi := api.Container(container)
		err = containerApi.Remove()

		// forced removal of container
		if err == storage.ErrorConianerNotEmpty && c.Flag("force").Get().(bool) {
			fmt.Println("removing all objects of", container)
			objects, err = containerApi.Objects()
			if err != nil {
				log.Fatal(err)
			}
			for _, object := range objects {
				err = object.Remove()
				// skipping NotFound errors as non-critical
				if err != nil && err != storage.ErrorObjectNotFound {
					log.Fatal(err)
				}
			}
			err = containerApi.Remove()
		}
		message = fmt.Sprintf("container %s removed", container)
	} else {
		err = api.Container(container).Object(object).Remove()
		message = fmt.Sprintf("object %s removed in container %s", object, container)
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(message)
}

func create(c cli.Command) {
	if len(c.Args()) == 0 {
		log.Fatal(errorNotEnough)
	}
	var name = c.Arg(0).String()
	if _, err := api.CreateContainer(name, false); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("created container %s\n", name)
}

func upload(c cli.Command) {
	var path string
	switch len(c.Args()) {
	case 1:
		path = c.Arg(0).String()
	case 2:
		container = c.Arg(0).String()
		path = c.Arg(1).String()
	}
	if blank(container) || blank(path) {
		log.Fatal(errorNotEnough)
	}
	if err := api.Container(container).UploadFile(path); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("uploaded to %s\n", container)
}

func list(c cli.Command) {
	var (
		arglen = len(c.Args())
		table  = tablewriter.NewWriter(os.Stdout)
	)
	if arglen == 0 && (blank(container) || c.Flag("type").String() == "storage") {
		containers, err := api.ContainersInfo()
		if err != nil {
			log.Fatal(err)
		}
		table.SetHeader([]string{"Name", "Objects", "Type"})
		for _, cont := range containers {
			v := []string{cont.Name, fmt.Sprint(cont.ObjectCount), cont.Type}
			table.Append(v)
		}
		table.Render()
		return
	}
	if arglen == 1 {
		container = c.Arg(0).String()
	}
	if blank(container) {
		log.Fatal(errorNotEnough)
	}
	objects, err := api.Container(container).ObjectsInfo()
	if err != nil {
		log.Fatal(err)
	}
	table.SetHeader([]string{"Name", "Size", "Downloaded"})
	for _, object := range objects {
		v := []string{object.Name, fmt.Sprint(object.Size), fmt.Sprint(object.Downloaded)}
		table.Append(v)
	}
	table.Render()
}

func download(c cli.Command) {
	var (
		arglen     = len(c.Args())
		objectName string
		path       = c.Flag("path").String()
	)
	switch arglen {
	case 1:
		objectName = c.Arg(0).String()
	case 2:
		objectName = c.Arg(1).String()
		container = c.Arg(0).String()
	}
	if blank(container) || blank(objectName) {
		log.Fatal(errorNotEnough)
	}
	if blank(path) {
		path = objectName
	}
	reader, err := api.Container(container).Object(objectName).GetReader()
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()
	fmt.Printf("downloading %s->%s from %s\n", objectName, path, container)
	f, err := os.Create(path)
	if err != nil {
		log.Fatal(err)
	}
	n, err := io.Copy(f, reader)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("downloaded %s, %d bytes\n", objectName, n)
}

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered", r)
		}
	}()
	client.Start()
}
<commit_msg>added progressbar for upload process<commit_after>package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/sha256"
	"errors"
	"fmt"
	"github.com/cheggaaa/pb"
	"github.com/ernado/selectel/storage"
	"github.com/jwaldrip/odin/cli"
	"github.com/olekukonko/tablewriter"
	"io"
	"io/ioutil"
	"log"
	"mime"
	"os"
	"path/filepath"
	"strings"
)

const (
	envKey        = storage.EnvKey
	envUser       = storage.EnvUser
	version       = "1.1"
	cacheFilename = "~selct.cache~" + version
	envCache      = "SELECTEL_CACHE"
	envContainer  = "SELECTEL_CONTAINER"
)

var (
	client         = cli.New(version, "Selectel storage command line client", connect)
	user, key      string
	container      string
	api            storage.API
	debug          bool
	cache          bool
	cacheSecure    bool
	errorNotEnough = errors.New("Not enought arguments")
)

func encryptionKey() []byte {
	hasher := sha256.New()
	hasher.Write([]byte("selectel storage command line client"))
	hasher.Write([]byte(key))
	hasher.Write([]byte(user))
	return hasher.Sum(nil)
}

func encrypt(data []byte) []byte {
	block, err := aes.NewCipher(encryptionKey())
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, aes.BlockSize+len(data))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}

	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], data)
	return ciphertext
}

func decrypt(data []byte) ([]byte, error) {
	block, err := aes.NewCipher(encryptionKey())
	if err != nil {
		return nil, err
	}

	if len(data) < aes.BlockSize {
		return nil, errors.New("ciphertext too short")
	}
	iv := data[:aes.BlockSize]
	data = data[aes.BlockSize:]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(data, data)

	return data, nil
}

func init() {
	client.DefineBoolFlagVar(&debug, "debug", false, "debug mode")
	client.DefineBoolFlagVar(&cache, "cache", false, fmt.Sprintf("cache token in file (%s)", envCache))
	client.DefineBoolFlagVar(&cacheSecure, "cache.secure", true, "encrypt/decrypt token with user-key pair (true by default)")
	client.DefineStringFlag("key", "", fmt.Sprintf("selectel storage key (%s)", envKey))
	client.AliasFlag('k', "key")
	client.DefineStringFlag("user", "", fmt.Sprintf("selectel storage user (%s)", envUser))
	client.AliasFlag('u', "user")
	client.DefineStringFlag("container", "", fmt.Sprintf("default container (%s)", envContainer))
	client.AliasFlag('c', "container")

	infoCommand := client.DefineSubCommand("info", "print information about storage/container/object", wrap(info))
	infoCommand.DefineStringFlag("type", "storage", "storage, container or object")
	infoCommand.AliasFlag('t', "type")

	listCommand := client.DefineSubCommand("list", "list objects in container/storage", wrap(list))
	listCommand.DefineStringFlag("type", "storage", "storage or container")
	listCommand.AliasFlag('t', "type")

	client.DefineSubCommand("upload", "upload object to container", wrap(upload))
	downloadCommand := client.DefineSubCommand("download", "download object from container", wrap(download))
	downloadCommand.DefineStringFlag("path", "", "destination path")
	downloadCommand.AliasFlag('p', "path")

	client.DefineSubCommand("create", "create container", wrap(create))

	removeCommand := client.DefineSubCommand("remove", "remove object or container", wrap(remove))
	removeCommand.DefineStringFlag("type", "object", "container or object")
	removeCommand.DefineBoolFlag("force", false, "remove container with files")
	removeCommand.AliasFlag('f', "force")
	removeCommand.AliasFlag('t', "type")
}

func readFlag(c cli.Command, name, env string) string {
	if len(os.Getenv(env)) > 0 {
		return os.Getenv(env)
	}
	return c.Flag(name).String()
}

func blank(s string) bool {
	return len(s) == 0
}

func load() ([]byte, error) {
	f, err := os.Open(cacheFilename)
	if err != nil {
		return nil, err
	}
	data, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	if !cacheSecure {
		return data, nil
	}
	return decrypt(data)
}

// connect reads credentials and performs auth
func connect(c cli.Command) {
	var err error

	key = readFlag(c, "key", envKey)
	user = readFlag(c, "user", envUser)
	container = readFlag(c, "container", envContainer)

	if strings.ToLower(os.Getenv(envCache)) == "true" {
		cache = true
	}

	if cache {
		var data []byte
		data, err = load()
		if err != nil {
			log.Println(err)
		} else {
			api, err = storage.NewFromCache(data)
			if err == nil {
				return
			} else {
				log.Println("unable to load from cache:", err)
			}
		}
	} else {
		os.Remove(cacheFilename)
	}

	// checking for blank credentials
	if blank(key) || blank(user) && api != nil {
		log.Fatal(storage.ErrorBadCredentials)
	}

	// connencting to api
	api = storage.NewAsync(user, key)
	api.Debug(debug)
	if err = api.Auth(user, key); err != nil {
		log.Fatal(err)
	}
}

func wrap(callback func(cli.Command)) func(cli.Command) {
	return func(c cli.Command) {
		connect(c.Parent())
		defer func() {
			if !cache {
				return
			}
			data, _ := api.Dump()
			if cacheSecure {
				data = encrypt(data)
			}
			f, _ := os.Create(cacheFilename)
			f.Write(data)
		}()
		callback(c)
	}
}

// info prints information about storage
func info(c cli.Command) {
	var (
		containerName = container
		objectName    string
		data          interface{}
		err           error
		arglen        = len(c.Args())
		command       = c.Flag("type").String()
	)

	defer func() {
		if err != nil {
			log.Fatal(err)
		}
		if blank(containerName) || command == "storage" {
			data = api.Info()
		} else {
			containerApi := api.Container(containerName)
			if blank(objectName) {
				data, err = containerApi.Info()
			} else {
				data, err = containerApi.Object(objectName).Info()
			}
		}
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%+v\n", data)
	}()

	if arglen > 0 {
		if command == "container" {
			containerName = c.Arg(0).String()
			return
		}
		command = "object"
		if !blank(containerName) && arglen == 1 {
			objectName = c.Arg(0).String()
			return
		}
		if arglen == 2 {
			containerName = c.Arg(0).String()
			objectName = c.Arg(1).String()
			return
		}
	}
	if command == "container" && !blank(containerName) {
		return
	}
	if command == "storage" {
		return
	}
	err = errorNotEnough
}

func remove(c cli.Command) {
	var (
		arglen  = len(c.Args())
		object  string
		err     error
		message string
		objects []storage.ObjectAPI
	)
	if arglen == 2 {
		container = c.Arg(0).String()
		object = c.Arg(1).String()
	}
	if arglen == 1 {
		if c.Flag("type").String() == "container" {
			container = c.Arg(0).String()
		} else {
			object = c.Arg(0).String()
		}
	}
	if blank(container) {
		log.Fatal(errorNotEnough)
	}
	if blank(object) {
		containerApi := api.Container(container)
		err = containerApi.Remove()

		// forced removal of container
		if err == storage.ErrorConianerNotEmpty && c.Flag("force").Get().(bool) {
			fmt.Println("removing all objects of", container)
			objects, err = containerApi.Objects()
			if err != nil {
				log.Fatal(err)
			}
			for _, object := range objects {
				err = object.Remove()
				// skipping NotFound errors as non-critical
				if err != nil && err != storage.ErrorObjectNotFound {
					log.Fatal(err)
				}
			}
			err = containerApi.Remove()
		}
		message = fmt.Sprintf("container %s removed", container)
	} else {
		err = api.Container(container).Object(object).Remove()
		message = fmt.Sprintf("object %s removed in container %s", object, container)
	}
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(message)
}

func create(c cli.Command) {
	if len(c.Args()) == 0 {
		log.Fatal(errorNotEnough)
	}
	var name = c.Arg(0).String()
	if _, err := api.CreateContainer(name, false); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("created container %s\n", name)
}

func upload(c cli.Command) {
	var path string
	switch len(c.Args()) {
	case 1:
		path = c.Arg(0).String()
	case 2:
		container = c.Arg(0).String()
		path = c.Arg(1).String()
	}
	if blank(container) || blank(path) {
		log.Fatal(errorNotEnough)
	}

	f, err := os.Open(path)
	if err != nil {
		log.Fatal(err)
	}

	stat, err := os.Stat(path)
	if err != nil {
		log.Fatal(err)
	}
	ext := filepath.Ext(path)
	mimetype := mime.TypeByExtension(ext)
	bar := pb.New64(stat.Size()).SetUnits(pb.U_BYTES)
	bar.Start()
	reader := io.TeeReader(f, bar)
	if err := api.Container(container).Upload(reader, stat.Name(), mimetype); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("uploaded to %s\n", container)
}

func list(c cli.Command) {
	var (
		arglen = len(c.Args())
		table  = tablewriter.NewWriter(os.Stdout)
	)
	if arglen == 0 && (blank(container) || c.Flag("type").String() == "storage") {
		containers, err := api.ContainersInfo()
		if err != nil {
			log.Fatal(err)
		}
		table.SetHeader([]string{"Name", "Objects", "Type"})
		for _, cont := range containers {
			v := []string{cont.Name, fmt.Sprint(cont.ObjectCount), cont.Type}
			table.Append(v)
		}
		table.Render()
		return
	}
	if arglen == 1 {
		container = c.Arg(0).String()
	}
	if blank(container) {
		log.Fatal(errorNotEnough)
	}
	objects, err := api.Container(container).ObjectsInfo()
	if err != nil {
		log.Fatal(err)
	}
	table.SetHeader([]string{"Name", "Size", "Downloaded"})
	for _, object := range objects {
		v := []string{object.Name, fmt.Sprint(object.Size), fmt.Sprint(object.Downloaded)}
		table.Append(v)
	}
	table.Render()
}

func download(c cli.Command) {
	var (
		arglen     = len(c.Args())
		objectName string
		path       = c.Flag("path").String()
	)
	switch arglen {
	case 1:
		objectName = c.Arg(0).String()
	case 2:
		objectName = c.Arg(1).String()
		container = c.Arg(0).String()
	}
	if blank(container) || blank(objectName) {
		log.Fatal(errorNotEnough)
	}
	if blank(path) {
		path = objectName
	}
	reader, err := api.Container(container).Object(objectName).GetReader()
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()
	fmt.Printf("downloading %s->%s from %s\n", objectName, path, container)
	f, err := os.Create(path)
	if err != nil {
		log.Fatal(err)
	}
	n, err := io.Copy(f, reader)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("downloaded %s, %d bytes\n", objectName, n)
}

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered", r)
		}
	}()
	client.Start()
}
<|endoftext|>


<commit_before>package test

import (
	"fmt"
	"github.com/danryan/hal"
)

func init() {
	hal.RegisterStore("memory", New)
}

func (s *store) Name() string { return "memory" }

type store struct {
	hal.BasicStore
	data map[string][]byte
}

// New returns an new initialized store
func New(robot *hal.Robot) (hal.Store, error) {
	s := &store{
		data: map[string][]byte{},
	}
	s.SetRobot(robot)
	return s, nil
}

func (s *store) Open() error {
	return nil
}

func (s *store) Close() error {
	return nil
}

func (s *store) Get(key string) ([]byte, error) {
	if val, ok := s.data[key]; ok {
		return val, nil
	}

	return nil, fmt.Errorf("key %s was not found", key)
}

func (s *store) Set(key string, data []byte) error {
	s.data[key] = data
	return nil
}

func (s *store) Delete(key string) error {
	if _, ok := s.data[key]; !ok {
		return fmt.Errorf("key %s was not found", key)
	}
	delete(s.data, key)
	return nil
}
<commit_msg>fix memory store package name<commit_after>package memory

import (
	"fmt"
	"github.com/danryan/hal"
)

func init() {
	hal.RegisterStore("memory", New)
}

func (s *store) Name() string { return "memory" }

type store struct {
	hal.BasicStore
	data map[string][]byte
}

// New returns an new initialized store
func New(robot *hal.Robot) (hal.Store, error) {
	s := &store{
		data: map[string][]byte{},
	}
	s.SetRobot(robot)
	return s, nil
}

func (s *store) Open() error {
	return nil
}

func (s *store) Close() error {
	return nil
}

func (s *store) Get(key string) ([]byte, error) {
	if val, ok := s.data[key]; ok {
		return val, nil
	}

	return nil, fmt.Errorf("key %s was not found", key)
}

func (s *store) Set(key string, data []byte) error {
	s.data[key] = data
	return nil
}

func (s *store) Delete(key string) error {
	if _, ok := s.data[key]; !ok {
		return fmt.Errorf("key %s was not found", key)
	}
	delete(s.data, key)
	return nil
}
<|endoftext|>


<commit_before>package secureoperator

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"net/http"
	"net/url"
)

const (
	// DNSNameMaxBytes is the maximum number of bytes a DNS name may contain
	DNSNameMaxBytes = 253
	// max number of characters in a 16-bit uint integer, converted to string
	extraPad         = 5
	paddingParameter = "random_padding"
)

// GDNSQuestion represents a question response item from Google's DNS service
// This is currently the same as DNSQuestion, our internal implementation, but
// since Google's API is in flux, we keep them separate
type GDNSQuestion DNSQuestion

// DNSQuestion transforms a GDNSQuestion to a DNSQuestion and returns it.
func (r GDNSQuestion) DNSQuestion() DNSQuestion {
	return DNSQuestion{
		Name: r.Name,
		Type: r.Type,
	}
}

// GDNSQuestions is a array of GDNSQuestion objects
type GDNSQuestions []GDNSQuestion

// DNSQuestions transforms an array of GDNSQuestion objects to an array of
// DNSQuestion objects
func (rs GDNSQuestions) DNSQuestions() (rqs []DNSQuestion) {
	for _, r := range rs {
		rqs = append(rqs, r.DNSQuestion())
	}

	return
}

// GDNSRR represents a dns response record item from Google's DNS service.
// This is currently the same as DNSRR, our internal implementation, but since
// Google's API is in flux, we keep them separate
type GDNSRR DNSRR

// DNSRR transforms a GDNSRR to a DNSRR
func (r GDNSRR) DNSRR() DNSRR {
	return DNSRR{
		Name: r.Name,
		Type: r.Type,
		TTL:  r.TTL,
		Data: r.Data,
	}
}

// GDNSRRs represents an array of GDNSRR objects
type GDNSRRs []GDNSRR

// DNSRRs transforms an array of GDNSRR objects to an array of DNSRR objects
func (rs GDNSRRs) DNSRRs() (rrs []DNSRR) {
	for _, r := range rs {
		rrs = append(rrs, r.DNSRR())
	}

	return
}

// GDNSResponse represents a response from the Google DNS-over-HTTPS servers
type GDNSResponse struct {
	Status           int32         `json:"Status,omitempty"`
	TC               bool          `json:"TC,omitempty"`
	RD               bool          `json:"RD,omitempty"`
	RA               bool          `json:"RA,omitempty"`
	AD               bool          `json:"AD,omitempty"`
	CD               bool          `json:"CD,omitempty"`
	Question         GDNSQuestions `json:"Question,omitempty"`
	Answer           GDNSRRs       `json:"Answer,omitempty"`
	Authority        GDNSRRs       `json:"Authority,omitempty"`
	Additional       GDNSRRs       `json:"Additional,omitempty"`
	EDNSClientSubnet string        `json:"edns_client_subnet,omitempty"`
	Comment          string        `json:"Comment,omitempty"`
}

// GDNSOptions is a configuration object for optional GDNSProvider configuration
type GDNSOptions struct {
	// Pad specifies if a DNS request should be padded to a fixed length
	Pad bool
	// EndpointIPs is a list of IPs to be used as the GDNS endpoint, avoiding
	// DNS lookups in the case where they are provided. One is chosen randomly
	// for each request.
	EndpointIPs []net.IP
	// DNSServers is a list of Endpoints to be used as DNS servers when looking
	// up the endpoint; if not provided, the system DNS resolver is used.
	DNSServers Endpoints
}

// NewGDNSProvider creates a GDNSProvider
func NewGDNSProvider(endpoint string, opts *GDNSOptions) (*GDNSProvider, error) {
	if opts == nil {
		opts = &GDNSOptions{}
	}

	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}

	g := &GDNSProvider{
		endpoint: endpoint,
		url:      u,
		host:     u.Host,
		opts:     opts,
	}

	if len(opts.DNSServers) > 0 {
		d, err := NewSimpleDNSClient(opts.DNSServers)
		if err != nil {
			return nil, err
		}

		g.dns = d
	}

	return g, nil
}

// GDNSProvider is the Google DNS-over-HTTPS provider; it implements the
// Provider interface.
type GDNSProvider struct {
	endpoint string
	url      *url.URL
	host     string
	opts     *GDNSOptions
	dns      *SimpleDNSClient
}

func (g GDNSProvider) newRequest(q DNSQuestion) (*http.Request, error) {
	u := *g.url

	var mustSendHost bool

	if l := len(g.opts.EndpointIPs); l > 0 {
		// if endpointIPs are provided, use one of those
		u.Host = g.opts.EndpointIPs[rand.Intn(l)].String()
		mustSendHost = true
	} else if g.dns != nil {
		ips, err := g.dns.LookupIP(u.Host)
		if err != nil {
			return nil, err
		}

		if l := len(ips); l > 0 {
			u.Host = ips[rand.Intn(l)].String()
		} else {
			return nil, fmt.Errorf("lookup for Google DNS host %v failed", u.Host)
		}
		mustSendHost = true
	}

	httpreq, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return nil, err
	}

	qry := httpreq.URL.Query()
	dnsType := fmt.Sprintf("%v", q.Type)

	l := len([]byte(q.Name))
	if l > DNSNameMaxBytes {
		return nil, fmt.Errorf("name length of %v exceeds DNS name max length", l)
	}

	qry.Add("name", q.Name)
	qry.Add("type", dnsType)
	qry.Add("edns_client_subnet", "0.0.0.0/0")

	httpreq.URL.RawQuery = qry.Encode()

	if g.opts.Pad {
		// pad to the maximum size a valid request could be. we add `1` because
		// Google's DNS service ignores a trailing period, increasing the
		// possible size of a name by 1
		pad := randSeq(DNSNameMaxBytes + extraPad - l - len(dnsType) + 1)
		qry.Add(paddingParameter, pad)

		httpreq.URL.RawQuery = qry.Encode()
	}

	if mustSendHost {
		httpreq.Host = g.url.Host
	}

	return httpreq, nil
}

// Query sends a DNS question to Google, and returns the response
func (g GDNSProvider) Query(q DNSQuestion) (*DNSResponse, error) {
	httpreq, err := g.newRequest(q)
	if err != nil {
		return nil, err
	}

	// custom transport for supporting servernames which may not match the url,
	// in cases where we request directly against an IP
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{ServerName: g.url.Host},
	}
	client := &http.Client{Transport: tr}

	httpresp, err := client.Do(httpreq)
	if err != nil {
		return nil, err
	}
	defer httpresp.Body.Close()

	dnsResp := new(GDNSResponse)
	decoder := json.NewDecoder(httpresp.Body)
	err = decoder.Decode(&dnsResp)
	if err != nil {
		return nil, err
	}

	return &DNSResponse{
		Question:           dnsResp.Question.DNSQuestions(),
		Answer:             dnsResp.Answer.DNSRRs(),
		Authority:          dnsResp.Authority.DNSRRs(),
		Extra:              dnsResp.Additional.DNSRRs(),
		Truncated:          dnsResp.TC,
		RecursionDesired:   dnsResp.RD,
		RecursionAvailable: dnsResp.RA,
		AuthenticatedData:  dnsResp.AD,
		CheckingDisabled:   dnsResp.CD,
		ResponseCode:       int(dnsResp.Status),
	}, nil
}
<commit_msg>fix socket leak (#8)<commit_after>package secureoperator

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"time"
)

const (
	// DNSNameMaxBytes is the maximum number of bytes a DNS name may contain
	DNSNameMaxBytes = 253
	// max number of characters in a 16-bit uint integer, converted to string
	extraPad         = 5
	paddingParameter = "random_padding"
)

// GDNSQuestion represents a question response item from Google's DNS service
// This is currently the same as DNSQuestion, our internal implementation, but
// since Google's API is in flux, we keep them separate
type GDNSQuestion DNSQuestion

// DNSQuestion transforms a GDNSQuestion to a DNSQuestion and returns it.
func (r GDNSQuestion) DNSQuestion() DNSQuestion {
	return DNSQuestion{
		Name: r.Name,
		Type: r.Type,
	}
}

// GDNSQuestions is a array of GDNSQuestion objects
type GDNSQuestions []GDNSQuestion

// DNSQuestions transforms an array of GDNSQuestion objects to an array of
// DNSQuestion objects
func (rs GDNSQuestions) DNSQuestions() (rqs []DNSQuestion) {
	for _, r := range rs {
		rqs = append(rqs, r.DNSQuestion())
	}

	return
}

// GDNSRR represents a dns response record item from Google's DNS service.
// This is currently the same as DNSRR, our internal implementation, but since
// Google's API is in flux, we keep them separate
type GDNSRR DNSRR

// DNSRR transforms a GDNSRR to a DNSRR
func (r GDNSRR) DNSRR() DNSRR {
	return DNSRR{
		Name: r.Name,
		Type: r.Type,
		TTL:  r.TTL,
		Data: r.Data,
	}
}

// GDNSRRs represents an array of GDNSRR objects
type GDNSRRs []GDNSRR

// DNSRRs transforms an array of GDNSRR objects to an array of DNSRR objects
func (rs GDNSRRs) DNSRRs() (rrs []DNSRR) {
	for _, r := range rs {
		rrs = append(rrs, r.DNSRR())
	}

	return
}

// GDNSResponse represents a response from the Google DNS-over-HTTPS servers
type GDNSResponse struct {
	Status           int32         `json:"Status,omitempty"`
	TC               bool          `json:"TC,omitempty"`
	RD               bool          `json:"RD,omitempty"`
	RA               bool          `json:"RA,omitempty"`
	AD               bool          `json:"AD,omitempty"`
	CD               bool          `json:"CD,omitempty"`
	Question         GDNSQuestions `json:"Question,omitempty"`
	Answer           GDNSRRs       `json:"Answer,omitempty"`
	Authority        GDNSRRs       `json:"Authority,omitempty"`
	Additional       GDNSRRs       `json:"Additional,omitempty"`
	EDNSClientSubnet string        `json:"edns_client_subnet,omitempty"`
	Comment          string        `json:"Comment,omitempty"`
}

// GDNSOptions is a configuration object for optional GDNSProvider configuration
type GDNSOptions struct {
	// Pad specifies if a DNS request should be padded to a fixed length
	Pad bool
	// EndpointIPs is a list of IPs to be used as the GDNS endpoint, avoiding
	// DNS lookups in the case where they are provided. One is chosen randomly
	// for each request.
	EndpointIPs []net.IP
	// DNSServers is a list of Endpoints to be used as DNS servers when looking
	// up the endpoint; if not provided, the system DNS resolver is used.
	DNSServers Endpoints
}

// NewGDNSProvider creates a GDNSProvider
func NewGDNSProvider(endpoint string, opts *GDNSOptions) (*GDNSProvider, error) {
	if opts == nil {
		opts = &GDNSOptions{}
	}

	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}

	g := &GDNSProvider{
		endpoint: endpoint,
		url:      u,
		host:     u.Host,
		opts:     opts,
	}

	if len(opts.DNSServers) > 0 {
		d, err := NewSimpleDNSClient(opts.DNSServers)
		if err != nil {
			return nil, err
		}

		g.dns = d
	}

	// custom transport for supporting servernames which may not match the url,
	// in cases where we request directly against an IP
	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
			DualStack: true,
		}).DialContext,
		MaxIdleConns:          100,
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,
		TLSClientConfig:       &tls.Config{ServerName: g.url.Host},
	}
	g.client = &http.Client{Transport: tr}

	return g, nil
}

// GDNSProvider is the Google DNS-over-HTTPS provider; it implements the
// Provider interface.
type GDNSProvider struct {
	endpoint string
	url      *url.URL
	host     string
	opts     *GDNSOptions
	dns      *SimpleDNSClient
	client   *http.Client
}

func (g GDNSProvider) newRequest(q DNSQuestion) (*http.Request, error) {
	u := *g.url

	var mustSendHost bool

	if l := len(g.opts.EndpointIPs); l > 0 {
		// if endpointIPs are provided, use one of those
		u.Host = g.opts.EndpointIPs[rand.Intn(l)].String()
		mustSendHost = true
	} else if g.dns != nil {
		ips, err := g.dns.LookupIP(u.Host)
		if err != nil {
			return nil, err
		}

		if l := len(ips); l > 0 {
			u.Host = ips[rand.Intn(l)].String()
		} else {
			return nil, fmt.Errorf("lookup for Google DNS host %v failed", u.Host)
		}
		mustSendHost = true
	}

	httpreq, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return nil, err
	}

	qry := httpreq.URL.Query()
	dnsType := fmt.Sprintf("%v", q.Type)

	l := len([]byte(q.Name))
	if l > DNSNameMaxBytes {
		return nil, fmt.Errorf("name length of %v exceeds DNS name max length", l)
	}

	qry.Add("name", q.Name)
	qry.Add("type", dnsType)
	qry.Add("edns_client_subnet", "0.0.0.0/0")

	httpreq.URL.RawQuery = qry.Encode()

	if g.opts.Pad {
		// pad to the maximum size a valid request could be. we add `1` because
		// Google's DNS service ignores a trailing period, increasing the
		// possible size of a name by 1
		pad := randSeq(DNSNameMaxBytes + extraPad - l - len(dnsType) + 1)
		qry.Add(paddingParameter, pad)

		httpreq.URL.RawQuery = qry.Encode()
	}

	if mustSendHost {
		httpreq.Host = g.url.Host
	}

	return httpreq, nil
}

// Query sends a DNS question to Google, and returns the response
func (g GDNSProvider) Query(q DNSQuestion) (*DNSResponse, error) {
	httpreq, err := g.newRequest(q)
	if err != nil {
		return nil, err
	}

	httpresp, err := g.client.Do(httpreq)
	if err != nil {
		return nil, err
	}
	defer httpresp.Body.Close()

	dnsResp := new(GDNSResponse)
	decoder := json.NewDecoder(httpresp.Body)
	err = decoder.Decode(&dnsResp)
	if err != nil {
		return nil, err
	}

	return &DNSResponse{
		Question:           dnsResp.Question.DNSQuestions(),
		Answer:             dnsResp.Answer.DNSRRs(),
		Authority:          dnsResp.Authority.DNSRRs(),
		Extra:              dnsResp.Additional.DNSRRs(),
		Truncated:          dnsResp.TC,
		RecursionDesired:   dnsResp.RD,
		RecursionAvailable: dnsResp.RA,
		AuthenticatedData:  dnsResp.AD,
		CheckingDisabled:   dnsResp.CD,
		ResponseCode:       int(dnsResp.Status),
	}, nil
}
<|endoftext|>