<commit_before>package driver

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

	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/go-plugin"
	"github.com/mitchellh/mapstructure"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/client/driver/executor"
	dstructs "github.com/hashicorp/nomad/client/driver/structs"
	"github.com/hashicorp/nomad/client/fingerprint"
	cstructs "github.com/hashicorp/nomad/client/structs"
	"github.com/hashicorp/nomad/helper/discover"
	"github.com/hashicorp/nomad/helper/fields"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	// The key populated in Node Attributes to indicate presence of the Java
	// driver
	javaDriverAttr = "driver.java"
)

// JavaDriver is a simple driver to execute applications packaged in Jars.
// It literally just fork/execs tasks with the java command.
type JavaDriver struct {
	DriverContext
	fingerprint.StaticFingerprinter
}

type JavaDriverConfig struct {
	JarPath string   `mapstructure:"jar_path"`
	JvmOpts []string `mapstructure:"jvm_options"`
	Args    []string `mapstructure:"args"`
}

// javaHandle is returned from Start/Open as a handle to the PID
type javaHandle struct {
	pluginClient    *plugin.Client
	userPid         int
	executor        executor.Executor
	isolationConfig *dstructs.IsolationConfig

	taskDir        string
	allocDir       *allocdir.AllocDir
	killTimeout    time.Duration
	maxKillTimeout time.Duration
	version        string
	logger         *log.Logger
	waitCh         chan *dstructs.WaitResult
	doneCh         chan struct{}
}

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

// Validate is used to validate the driver configuration
func (d *JavaDriver) Validate(config map[string]interface{}) error {
	fd := &fields.FieldData{
		Raw: config,
		Schema: map[string]*fields.FieldSchema{
			"jar_path": &fields.FieldSchema{
				Type:     fields.TypeString,
				Required: true,
			},
			"jvm_options": &fields.FieldSchema{
				Type: fields.TypeArray,
			},
			"args": &fields.FieldSchema{
				Type: fields.TypeArray,
			},
		},
	}

	if err := fd.Validate(); err != nil {
		return err
	}

	return nil
}

func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
	// Get the current status so that we can log any debug messages only if the
	// state changes
	_, currentlyEnabled := node.Attributes[javaDriverAttr]

	// Only enable if we are root and cgroups are mounted when running on linux systems.
	if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !d.cgroupsMounted(node)) {
		if currentlyEnabled {
			d.logger.Printf("[DEBUG] driver.java: root priviledges and mounted cgroups required on linux, disabling")
		}
		delete(node.Attributes, "driver.java")
		return false, nil
	}

	// Find java version
	var out bytes.Buffer
	var erOut bytes.Buffer
	cmd := exec.Command("java", "-version")
	cmd.Stdout = &out
	cmd.Stderr = &erOut
	err := cmd.Run()
	if err != nil {
		// assume Java wasn't found
		delete(node.Attributes, javaDriverAttr)
		return false, nil
	}

	// 'java -version' returns output on Stderr typically.
	// Check stdout, but it's probably empty
	var infoString string
	if out.String() != "" {
		infoString = out.String()
	}

	if erOut.String() != "" {
		infoString = erOut.String()
	}

	if infoString == "" {
		if currentlyEnabled {
			d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting")
		}
		delete(node.Attributes, javaDriverAttr)
		return false, nil
	}

	// Assume 'java -version' returns 3 lines:
	//    java version "1.6.0_36"
	//    OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04)
	//    OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
	// Each line is terminated by \n
	info := strings.Split(infoString, "\n")
	versionString := info[0]
	versionString = strings.TrimPrefix(versionString, "java version ")
	versionString = strings.Trim(versionString, "\"")
	node.Attributes[javaDriverAttr] = "1"
	node.Attributes["driver.java.version"] = versionString
	node.Attributes["driver.java.runtime"] = info[1]
	node.Attributes["driver.java.vm"] = info[2]

	return true, nil
}

func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
	var driverConfig JavaDriverConfig
	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
		return nil, err
	}

	// Set the host environment variables.
	filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",")
	d.taskEnv.AppendHostEnvvars(filter)

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

	if driverConfig.JarPath == "" {
		return nil, fmt.Errorf("jar_path must be specified")
	}

	args := []string{}
	// Look for jvm options
	if len(driverConfig.JvmOpts) != 0 {
		d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts)
		args = append(args, driverConfig.JvmOpts...)
	}

	// Build the argument list.
	args = append(args, "-jar", driverConfig.JarPath)
	if len(driverConfig.Args) != 0 {
		args = append(args, driverConfig.Args...)
	}

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

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

	execIntf, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		return nil, err
	}
	executorCtx := &executor.ExecutorContext{
		TaskEnv:  d.taskEnv,
		Driver:   "java",
		AllocDir: ctx.AllocDir,
		AllocID:  ctx.AllocID,
		Task:     task,
	}

	absPath, err := GetAbsolutePath("java")
	if err != nil {
		return nil, err
	}

	ps, err := execIntf.LaunchCmd(&executor.ExecCommand{
		Cmd:            absPath,
		Args:           args,
		FSIsolation:    true,
		ResourceLimits: true,
		User:           getExecutorUser(task),
	}, executorCtx)
	if err != nil {
		pluginClient.Kill()
		return nil, err
	}
	d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid)

	// Return a driver handle
	maxKill := d.DriverContext.config.MaxKillTimeout
	h := &javaHandle{
		pluginClient:    pluginClient,
		executor:        execIntf,
		userPid:         ps.Pid,
		isolationConfig: ps.IsolationConfig,
		taskDir:         taskDir,
		allocDir:        ctx.AllocDir,
		killTimeout:     GetKillTimeout(task.KillTimeout, maxKill),
		maxKillTimeout:  maxKill,
		version:         d.config.Version,
		logger:          d.logger,
		doneCh:          make(chan struct{}),
		waitCh:          make(chan *dstructs.WaitResult, 1),
	}
	if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil {
		d.logger.Printf("[ERR] driver.java: error registering services with consul for task: %q: %v", task.Name, err)
	}
	go h.run()
	return h, nil
}

// cgroupsMounted returns true if the cgroups are mounted on a system otherwise
// returns false
func (d *JavaDriver) cgroupsMounted(node *structs.Node) bool {
	_, ok := node.Attributes["unique.cgroup.mountpoint"]
	return ok
}

type javaId struct {
	Version         string
	KillTimeout     time.Duration
	MaxKillTimeout  time.Duration
	PluginConfig    *PluginReattachConfig
	IsolationConfig *dstructs.IsolationConfig
	TaskDir         string
	AllocDir        *allocdir.AllocDir
	UserPid         int
}

func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
	id := &javaId{}
	if err := json.Unmarshal([]byte(handleID), id); err != nil {
		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
	}

	pluginConfig := &plugin.ClientConfig{
		Reattach: id.PluginConfig.PluginConfig(),
	}
	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		merrs := new(multierror.Error)
		merrs.Errors = append(merrs.Errors, err)
		d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid")
		if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil {
			merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e))
		}
		if id.IsolationConfig != nil {
			ePid := pluginConfig.Reattach.Pid
			if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil {
				merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying resource container failed: %v", e))
			}
		}
		if e := ctx.AllocDir.UnmountAll(); e != nil {
			merrs.Errors = append(merrs.Errors, e)
		}

		return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil())
	}

	ver, _ := exec.Version()
	d.logger.Printf("[DEBUG] driver.java: version of executor: %v", ver.Version)

	// Return a driver handle
	h := &javaHandle{
		pluginClient:    pluginClient,
		executor:        exec,
		userPid:         id.UserPid,
		isolationConfig: id.IsolationConfig,
		taskDir:         id.TaskDir,
		allocDir:        id.AllocDir,
		logger:          d.logger,
		version:         id.Version,
		killTimeout:     id.KillTimeout,
		maxKillTimeout:  id.MaxKillTimeout,
		doneCh:          make(chan struct{}),
		waitCh:          make(chan *dstructs.WaitResult, 1),
	}
	if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil {
		d.logger.Printf("[ERR] driver.java: error registering services with consul: %v", err)
	}

	go h.run()
	return h, nil
}

func (h *javaHandle) ID() string {
	id := javaId{
		Version:         h.version,
		KillTimeout:     h.killTimeout,
		MaxKillTimeout:  h.maxKillTimeout,
		PluginConfig:    NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
		UserPid:         h.userPid,
		TaskDir:         h.taskDir,
		AllocDir:        h.allocDir,
		IsolationConfig: h.isolationConfig,
	}

	data, err := json.Marshal(id)
	if err != nil {
		h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err)
	}
	return string(data)
}

func (h *javaHandle) WaitCh() chan *dstructs.WaitResult {
	return h.waitCh
}

func (h *javaHandle) Update(task *structs.Task) error {
	// Store the updated kill timeout.
	h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout)
	h.executor.UpdateTask(task)

	// Update is not possible
	return nil
}

func (h *javaHandle) Kill() error {
	if err := h.executor.ShutDown(); err != nil {
		if h.pluginClient.Exited() {
			return nil
		}
		return fmt.Errorf("executor Shutdown failed: %v", err)
	}

	select {
	case <-h.doneCh:
		return nil
	case <-time.After(h.killTimeout):
		if h.pluginClient.Exited() {
			return nil
		}
		if err := h.executor.Exit(); err != nil {
			return fmt.Errorf("executor Exit failed: %v", err)
		}

		return nil
	}
}

func (h *javaHandle) Stats() (*cstructs.TaskResourceUsage, error) {
	return h.executor.Stats()
}

func (h *javaHandle) run() {
	ps, err := h.executor.Wait()
	close(h.doneCh)
	if ps.ExitCode == 0 && err != nil {
		if h.isolationConfig != nil {
			ePid := h.pluginClient.ReattachConfig().Pid
			if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil {
				h.logger.Printf("[ERR] driver.java: destroying resource container failed: %v", e)
			}
		} else {
			if e := killProcess(h.userPid); e != nil {
				h.logger.Printf("[ERR] driver.java: error killing user process: %v", e)
			}
		}
		if e := h.allocDir.UnmountAll(); e != nil {
			h.logger.Printf("[ERR] driver.java: unmounting dev,proc and alloc dirs failed: %v", e)
		}
	}
	h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: err}
	close(h.waitCh)

	// Remove services
	if err := h.executor.DeregisterServices(); err != nil {
		h.logger.Printf("[ERR] driver.java: failed to kill the deregister services: %v", err)
	}

	h.executor.Exit()
	h.pluginClient.Kill()
}
<commit_msg>Add chroot_env to Java driver (which uses the exec driver internally)<commit_after>package driver

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

	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/go-plugin"
	"github.com/mitchellh/mapstructure"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/client/driver/executor"
	dstructs "github.com/hashicorp/nomad/client/driver/structs"
	"github.com/hashicorp/nomad/client/fingerprint"
	cstructs "github.com/hashicorp/nomad/client/structs"
	"github.com/hashicorp/nomad/helper/discover"
	"github.com/hashicorp/nomad/helper/fields"
	"github.com/hashicorp/nomad/nomad/structs"
)

const (
	// The key populated in Node Attributes to indicate presence of the Java
	// driver
	javaDriverAttr = "driver.java"
)

// JavaDriver is a simple driver to execute applications packaged in Jars.
// It literally just fork/execs tasks with the java command.
type JavaDriver struct {
	DriverContext
	fingerprint.StaticFingerprinter
}

type JavaDriverConfig struct {
	JarPath string   `mapstructure:"jar_path"`
	JvmOpts []string `mapstructure:"jvm_options"`
	Args    []string `mapstructure:"args"`
}

// javaHandle is returned from Start/Open as a handle to the PID
type javaHandle struct {
	pluginClient    *plugin.Client
	userPid         int
	executor        executor.Executor
	isolationConfig *dstructs.IsolationConfig

	taskDir        string
	allocDir       *allocdir.AllocDir
	killTimeout    time.Duration
	maxKillTimeout time.Duration
	version        string
	logger         *log.Logger
	waitCh         chan *dstructs.WaitResult
	doneCh         chan struct{}
}

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

// Validate is used to validate the driver configuration
func (d *JavaDriver) Validate(config map[string]interface{}) error {
	fd := &fields.FieldData{
		Raw: config,
		Schema: map[string]*fields.FieldSchema{
			"jar_path": &fields.FieldSchema{
				Type:     fields.TypeString,
				Required: true,
			},
			"jvm_options": &fields.FieldSchema{
				Type: fields.TypeArray,
			},
			"args": &fields.FieldSchema{
				Type: fields.TypeArray,
			},
		},
	}

	if err := fd.Validate(); err != nil {
		return err
	}

	return nil
}

func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
	// Get the current status so that we can log any debug messages only if the
	// state changes
	_, currentlyEnabled := node.Attributes[javaDriverAttr]

	// Only enable if we are root and cgroups are mounted when running on linux systems.
	if runtime.GOOS == "linux" && (syscall.Geteuid() != 0 || !d.cgroupsMounted(node)) {
		if currentlyEnabled {
			d.logger.Printf("[DEBUG] driver.java: root priviledges and mounted cgroups required on linux, disabling")
		}
		delete(node.Attributes, "driver.java")
		return false, nil
	}

	// Find java version
	var out bytes.Buffer
	var erOut bytes.Buffer
	cmd := exec.Command("java", "-version")
	cmd.Stdout = &out
	cmd.Stderr = &erOut
	err := cmd.Run()
	if err != nil {
		// assume Java wasn't found
		delete(node.Attributes, javaDriverAttr)
		return false, nil
	}

	// 'java -version' returns output on Stderr typically.
	// Check stdout, but it's probably empty
	var infoString string
	if out.String() != "" {
		infoString = out.String()
	}

	if erOut.String() != "" {
		infoString = erOut.String()
	}

	if infoString == "" {
		if currentlyEnabled {
			d.logger.Println("[WARN] driver.java: error parsing Java version information, aborting")
		}
		delete(node.Attributes, javaDriverAttr)
		return false, nil
	}

	// Assume 'java -version' returns 3 lines:
	//    java version "1.6.0_36"
	//    OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04)
	//    OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode)
	// Each line is terminated by \n
	info := strings.Split(infoString, "\n")
	versionString := info[0]
	versionString = strings.TrimPrefix(versionString, "java version ")
	versionString = strings.Trim(versionString, "\"")
	node.Attributes[javaDriverAttr] = "1"
	node.Attributes["driver.java.version"] = versionString
	node.Attributes["driver.java.runtime"] = info[1]
	node.Attributes["driver.java.vm"] = info[2]

	return true, nil
}

func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
	var driverConfig JavaDriverConfig
	if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
		return nil, err
	}

	// Set the host environment variables.
	filter := strings.Split(d.config.ReadDefault("env.blacklist", config.DefaultEnvBlacklist), ",")
	d.taskEnv.AppendHostEnvvars(filter)

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

	if driverConfig.JarPath == "" {
		return nil, fmt.Errorf("jar_path must be specified")
	}

	args := []string{}
	// Look for jvm options
	if len(driverConfig.JvmOpts) != 0 {
		d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts)
		args = append(args, driverConfig.JvmOpts...)
	}

	// Build the argument list.
	args = append(args, "-jar", driverConfig.JarPath)
	if len(driverConfig.Args) != 0 {
		args = append(args, driverConfig.Args...)
	}

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

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

	execIntf, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		return nil, err
	}
	executorCtx := &executor.ExecutorContext{
		TaskEnv:  d.taskEnv,
		Driver:   "java",
		AllocDir: ctx.AllocDir,
		AllocID:  ctx.AllocID,
		Task:     task,
	}

	absPath, err := GetAbsolutePath("java")
	if err != nil {
		return nil, err
	}

	ps, err := execIntf.LaunchCmd(&executor.ExecCommand{
		Cmd:            absPath,
		Args:           args,
		FSIsolation:    true,
		ChrootEnv:      d.config.ChrootEnv,
		ResourceLimits: true,
		User:           getExecutorUser(task),
	}, executorCtx)
	if err != nil {
		pluginClient.Kill()
		return nil, err
	}
	d.logger.Printf("[DEBUG] driver.java: started process with pid: %v", ps.Pid)

	// Return a driver handle
	maxKill := d.DriverContext.config.MaxKillTimeout
	h := &javaHandle{
		pluginClient:    pluginClient,
		executor:        execIntf,
		userPid:         ps.Pid,
		isolationConfig: ps.IsolationConfig,
		taskDir:         taskDir,
		allocDir:        ctx.AllocDir,
		killTimeout:     GetKillTimeout(task.KillTimeout, maxKill),
		maxKillTimeout:  maxKill,
		version:         d.config.Version,
		logger:          d.logger,
		doneCh:          make(chan struct{}),
		waitCh:          make(chan *dstructs.WaitResult, 1),
	}
	if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil {
		d.logger.Printf("[ERR] driver.java: error registering services with consul for task: %q: %v", task.Name, err)
	}
	go h.run()
	return h, nil
}

// cgroupsMounted returns true if the cgroups are mounted on a system otherwise
// returns false
func (d *JavaDriver) cgroupsMounted(node *structs.Node) bool {
	_, ok := node.Attributes["unique.cgroup.mountpoint"]
	return ok
}

type javaId struct {
	Version         string
	KillTimeout     time.Duration
	MaxKillTimeout  time.Duration
	PluginConfig    *PluginReattachConfig
	IsolationConfig *dstructs.IsolationConfig
	TaskDir         string
	AllocDir        *allocdir.AllocDir
	UserPid         int
}

func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error) {
	id := &javaId{}
	if err := json.Unmarshal([]byte(handleID), id); err != nil {
		return nil, fmt.Errorf("Failed to parse handle '%s': %v", handleID, err)
	}

	pluginConfig := &plugin.ClientConfig{
		Reattach: id.PluginConfig.PluginConfig(),
	}
	exec, pluginClient, err := createExecutor(pluginConfig, d.config.LogOutput, d.config)
	if err != nil {
		merrs := new(multierror.Error)
		merrs.Errors = append(merrs.Errors, err)
		d.logger.Println("[ERR] driver.java: error connecting to plugin so destroying plugin pid and user pid")
		if e := destroyPlugin(id.PluginConfig.Pid, id.UserPid); e != nil {
			merrs.Errors = append(merrs.Errors, fmt.Errorf("error destroying plugin and userpid: %v", e))
		}
		if id.IsolationConfig != nil {
			ePid := pluginConfig.Reattach.Pid
			if e := executor.ClientCleanup(id.IsolationConfig, ePid); e != nil {
				merrs.Errors = append(merrs.Errors, fmt.Errorf("destroying resource container failed: %v", e))
			}
		}
		if e := ctx.AllocDir.UnmountAll(); e != nil {
			merrs.Errors = append(merrs.Errors, e)
		}

		return nil, fmt.Errorf("error connecting to plugin: %v", merrs.ErrorOrNil())
	}

	ver, _ := exec.Version()
	d.logger.Printf("[DEBUG] driver.java: version of executor: %v", ver.Version)

	// Return a driver handle
	h := &javaHandle{
		pluginClient:    pluginClient,
		executor:        exec,
		userPid:         id.UserPid,
		isolationConfig: id.IsolationConfig,
		taskDir:         id.TaskDir,
		allocDir:        id.AllocDir,
		logger:          d.logger,
		version:         id.Version,
		killTimeout:     id.KillTimeout,
		maxKillTimeout:  id.MaxKillTimeout,
		doneCh:          make(chan struct{}),
		waitCh:          make(chan *dstructs.WaitResult, 1),
	}
	if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil {
		d.logger.Printf("[ERR] driver.java: error registering services with consul: %v", err)
	}

	go h.run()
	return h, nil
}

func (h *javaHandle) ID() string {
	id := javaId{
		Version:         h.version,
		KillTimeout:     h.killTimeout,
		MaxKillTimeout:  h.maxKillTimeout,
		PluginConfig:    NewPluginReattachConfig(h.pluginClient.ReattachConfig()),
		UserPid:         h.userPid,
		TaskDir:         h.taskDir,
		AllocDir:        h.allocDir,
		IsolationConfig: h.isolationConfig,
	}

	data, err := json.Marshal(id)
	if err != nil {
		h.logger.Printf("[ERR] driver.java: failed to marshal ID to JSON: %s", err)
	}
	return string(data)
}

func (h *javaHandle) WaitCh() chan *dstructs.WaitResult {
	return h.waitCh
}

func (h *javaHandle) Update(task *structs.Task) error {
	// Store the updated kill timeout.
	h.killTimeout = GetKillTimeout(task.KillTimeout, h.maxKillTimeout)
	h.executor.UpdateTask(task)

	// Update is not possible
	return nil
}

func (h *javaHandle) Kill() error {
	if err := h.executor.ShutDown(); err != nil {
		if h.pluginClient.Exited() {
			return nil
		}
		return fmt.Errorf("executor Shutdown failed: %v", err)
	}

	select {
	case <-h.doneCh:
		return nil
	case <-time.After(h.killTimeout):
		if h.pluginClient.Exited() {
			return nil
		}
		if err := h.executor.Exit(); err != nil {
			return fmt.Errorf("executor Exit failed: %v", err)
		}

		return nil
	}
}

func (h *javaHandle) Stats() (*cstructs.TaskResourceUsage, error) {
	return h.executor.Stats()
}

func (h *javaHandle) run() {
	ps, err := h.executor.Wait()
	close(h.doneCh)
	if ps.ExitCode == 0 && err != nil {
		if h.isolationConfig != nil {
			ePid := h.pluginClient.ReattachConfig().Pid
			if e := executor.ClientCleanup(h.isolationConfig, ePid); e != nil {
				h.logger.Printf("[ERR] driver.java: destroying resource container failed: %v", e)
			}
		} else {
			if e := killProcess(h.userPid); e != nil {
				h.logger.Printf("[ERR] driver.java: error killing user process: %v", e)
			}
		}
		if e := h.allocDir.UnmountAll(); e != nil {
			h.logger.Printf("[ERR] driver.java: unmounting dev,proc and alloc dirs failed: %v", e)
		}
	}
	h.waitCh <- &dstructs.WaitResult{ExitCode: ps.ExitCode, Signal: ps.Signal, Err: err}
	close(h.waitCh)

	// Remove services
	if err := h.executor.DeregisterServices(); err != nil {
		h.logger.Printf("[ERR] driver.java: failed to kill the deregister services: %v", err)
	}

	h.executor.Exit()
	h.pluginClient.Kill()
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package gen

import (
	"fmt"

	"github.com/uber/thriftrw-go/compile"
)

// listValueList generates a new ValueList type alias for the given list.
//
// The following is generated:
//
// 	type $valueListName []$valueType
//
// 	func (v $valueListName) ForEach(f func(wire.Value) error) error { ... }
//
// 	func (v $valueListName) Close() { ... }
//
// And $valueListName is returned. This may be used where a ValueList of the
// given type is expected.
func (g *Generator) listValueList(spec *compile.ListSpec) (string, error) {
	name := "_" + valueName(spec) + "_ValueList"
	if _, ok := g.listValueLists[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			type <.Name> <typeReference .Spec Required>

			<$v := newVar "v">
			<$x := newVar "x">
			<$f := newVar "f">
			func (<$v> <.Name>) ForEach(<$f> func(<$wire>.Value) error) error {
				for _, <$x> := range <$v> {
					err := <$f>(<toWire .Spec.ValueSpec $x>)
					if err != nil {
						return err
					}
				}
				return nil
			}

			func (<$v> <.Name>) Close() {}
		`,
		struct {
			Name string
			Spec *compile.ListSpec
		}{Name: name, Spec: spec},
	)
	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.listValueLists[name] = struct{}{}
	return name, nil
}

func (g *Generator) listReader(spec *compile.ListSpec) (string, error) {
	name := "_" + valueName(spec) + "_Read"
	if _, ok := g.listReaders[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			<$listType := typeReference .Spec Required>

			<$l := newVar "l">
			<$i := newVar "i">
			<$o := newVar "o">
			<$x := newVar "x">
			func <.Name>(<$l> <$wire>.List) <$listType> {
				if <$l>.ValueType != <typeCode .Spec.ValueSpec> {
					return nil
				}

				<$o> := make(<$listType>, 0, <$l>.Size)
				<$l>.Items.ForEach(func(<$x> <$wire>.Value) error {
					var <$i> <typeReference .Spec.ValueSpec Required>
					<fromWire .Spec.ValueSpec $i $x>
					// TODO error handling
					<$o> = append(<$o>, <$i>)
					return nil
				})
				<$l>.Items.Close()
				return <$o>
			}
		`,
		struct {
			Name string
			Spec *compile.ListSpec
		}{Name: name, Spec: spec},
	)

	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.listReaders[name] = struct{}{}
	return name, nil
}

// setValueList generates a new ValueList type alias for the given set.
//
// The following is generated:
//
// 	type $valueListName map[$valueType]struct{}
//
// 	func (v $valueListName) ForEach(f func(wire.Value) error) error { ... }
//
// 	func (v $valueListName) Close() { ... }
//
// And $valueListName is returned. This may be used where a ValueList of the
// given type is expected.
func (g *Generator) setValueList(spec *compile.SetSpec) (string, error) {
	// TODO(abg): Unhashable types
	name := "_" + valueName(spec) + "_ValueList"
	if _, ok := g.setValueLists[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			type <.Name> <typeReference .Spec Required>

			<$v := newVar "v">
			<$x := newVar "x">
			<$f := newVar "f">
			func (<$v> <.Name>) ForEach(<$f> func(<$wire>.Value) error) error {
				for <$x> := range <$v> {
					err := <$f>(<toWire .Spec.ValueSpec $x>)
					if err != nil {
						return err
					}
				}
				return nil
			}

			func (<$v> <.Name>) Close() {}
		`,
		struct {
			Name string
			Spec *compile.SetSpec
		}{Name: name, Spec: spec},
	)
	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.setValueLists[name] = struct{}{}
	return name, nil
}

func (g *Generator) setReader(spec *compile.SetSpec) (string, error) {
	name := "_" + valueName(spec) + "_Read"
	if _, ok := g.setReaders[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			<$setType := typeReference .Spec Required>

			<$s := newVar "s">
			<$i := newVar "i">
			<$o := newVar "o">
			<$x := newVar "x">
			func <.Name>(<$s> <$wire>.Set) <$setType> {
				if <$s>.ValueType != <typeCode .Spec.ValueSpec> {
					return nil
				}

				<$o> := make(<$setType>, <$s>.Size)
				<$s>.Items.ForEach(func(<$x> <$wire>.Value) error {
					var <$i> <typeReference .Spec.ValueSpec Required>
					<fromWire .Spec.ValueSpec $i $x>
					// TODO error handling
					<$o>[<$i>] = struct{}{}
					return nil
				})
				<$s>.Items.Close()
				return <$o>
			}
		`,
		struct {
			Name string
			Spec *compile.SetSpec
		}{Name: name, Spec: spec},
	)

	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.setReaders[name] = struct{}{}
	return name, nil
}

// mapItemList generates a new MapItemList type alias for the given map.
//
// The following is generated:
//
// 	type $mapItemListName map[$keyType]$valueType
//
// 	func (v $mapItemListName) ForEach(f func(wire.MapItem) error) error { ... }
//
// 	func (v $mapItemListName) Close() { ... }
//
// And $mapItemListName is returned. This may be used where a MapItemList of the
// given type is expected.
func (g *Generator) mapItemList(spec *compile.MapSpec) (string, error) {
	// TODO(abg): Unhashable types
	name := "_" + valueName(spec) + "_MapItemList"
	if _, ok := g.mapItemLists[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			type <.Name> <typeReference .Spec Required>

			<$m := newVar "m">
			<$f := newVar "f">
			<$k := newVar "k">
			<$v := newVar "v">
			func (<$m> <.Name>) ForEach(<$f> func(<$wire>.MapItem) error) error {
				for <$k>, <$v> := range <$m> {
					err := <$f>(<$wire>.MapItem{
						Key: <toWire .Spec.KeySpec $k>,
						Value: <toWire .Spec.ValueSpec $v>,
					})
					if err != nil {
						return err
					}
				}
				return nil
			}

			func (<$m> <.Name>) Close() {}
		`,
		struct {
			Name string
			Spec *compile.MapSpec
		}{Name: name, Spec: spec},
	)
	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.mapItemLists[name] = struct{}{}
	return name, nil
}

func (g *Generator) mapReader(spec *compile.MapSpec) (string, error) {
	name := "_" + valueName(spec) + "_Read"
	if _, ok := g.mapReaders[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			<$mapType := typeReference .Spec Required>

			<$m := newVar "m">
			<$o := newVar "o">
			<$x := newVar "x">
			<$k := newVar "k">
			<$v := newVar "v">
			func <.Name>(<$m> <$wire>.Map) <$mapType> {
				if <$m>.KeyType != <typeCode .Spec.KeySpec> {
					return nil
				}

				if <$m>.ValueType != <typeCode .Spec.ValueSpec> {
					return nil
				}

				<$o> := make(<$mapType>, <$m>.Size)
				<$m>.Items.ForEach(func(<$x> <$wire>.MapItem) error {
					var <$k> <typeReference .Spec.KeySpec Required>
					var <$v> <typeReference .Spec.ValueSpec Required>
					<fromWire .Spec.KeySpec $k (printf "%s.Key" $x)>
					<fromWire .Spec.ValueSpec $v (printf "%s.Value" $x)>
					// TODO error handling
					<$o>[<$k>] = <$v>
					return nil
				})
				<$m>.Items.Close()
				return <$o>
			}
		`,
		struct {
			Name string
			Spec *compile.MapSpec
		}{Name: name, Spec: spec},
	)

	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.mapReaders[name] = struct{}{}
	return name, nil
}

func valueName(spec compile.TypeSpec) string {
	switch spec {
	case compile.BoolSpec:
		return "Bool"
	case compile.I8Spec:
		return "I8"
	case compile.I16Spec:
		return "I16"
	case compile.I32Spec:
		return "I32"
	case compile.I64Spec:
		return "I64"
	case compile.DoubleSpec:
		return "Double"
	case compile.StringSpec:
		return "String"
	case compile.BinarySpec:
		return "Binary"
	default:
		// Not a primitive type
	}

	switch s := spec.(type) {
	case *compile.MapSpec:
		return fmt.Sprintf(
			"Map_%s_%s", valueName(s.KeySpec), valueName(s.ValueSpec),
		)
	case *compile.ListSpec:
		return fmt.Sprintf("List_%s", valueName(s.ValueSpec))
	case *compile.SetSpec:
		return fmt.Sprintf("Set_%s", valueName(s.ValueSpec))
	default:
		return typeDeclName(spec)
	}
}
<commit_msg>Simplify valueName<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package gen

import (
	"fmt"

	"github.com/uber/thriftrw-go/compile"
)

// listValueList generates a new ValueList type alias for the given list.
//
// The following is generated:
//
// 	type $valueListName []$valueType
//
// 	func (v $valueListName) ForEach(f func(wire.Value) error) error { ... }
//
// 	func (v $valueListName) Close() { ... }
//
// And $valueListName is returned. This may be used where a ValueList of the
// given type is expected.
func (g *Generator) listValueList(spec *compile.ListSpec) (string, error) {
	name := "_" + valueName(spec) + "_ValueList"
	if _, ok := g.listValueLists[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			type <.Name> <typeReference .Spec Required>

			<$v := newVar "v">
			<$x := newVar "x">
			<$f := newVar "f">
			func (<$v> <.Name>) ForEach(<$f> func(<$wire>.Value) error) error {
				for _, <$x> := range <$v> {
					err := <$f>(<toWire .Spec.ValueSpec $x>)
					if err != nil {
						return err
					}
				}
				return nil
			}

			func (<$v> <.Name>) Close() {}
		`,
		struct {
			Name string
			Spec *compile.ListSpec
		}{Name: name, Spec: spec},
	)
	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.listValueLists[name] = struct{}{}
	return name, nil
}

func (g *Generator) listReader(spec *compile.ListSpec) (string, error) {
	name := "_" + valueName(spec) + "_Read"
	if _, ok := g.listReaders[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			<$listType := typeReference .Spec Required>

			<$l := newVar "l">
			<$i := newVar "i">
			<$o := newVar "o">
			<$x := newVar "x">
			func <.Name>(<$l> <$wire>.List) <$listType> {
				if <$l>.ValueType != <typeCode .Spec.ValueSpec> {
					return nil
				}

				<$o> := make(<$listType>, 0, <$l>.Size)
				<$l>.Items.ForEach(func(<$x> <$wire>.Value) error {
					var <$i> <typeReference .Spec.ValueSpec Required>
					<fromWire .Spec.ValueSpec $i $x>
					// TODO error handling
					<$o> = append(<$o>, <$i>)
					return nil
				})
				<$l>.Items.Close()
				return <$o>
			}
		`,
		struct {
			Name string
			Spec *compile.ListSpec
		}{Name: name, Spec: spec},
	)

	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.listReaders[name] = struct{}{}
	return name, nil
}

// setValueList generates a new ValueList type alias for the given set.
//
// The following is generated:
//
// 	type $valueListName map[$valueType]struct{}
//
// 	func (v $valueListName) ForEach(f func(wire.Value) error) error { ... }
//
// 	func (v $valueListName) Close() { ... }
//
// And $valueListName is returned. This may be used where a ValueList of the
// given type is expected.
func (g *Generator) setValueList(spec *compile.SetSpec) (string, error) {
	// TODO(abg): Unhashable types
	name := "_" + valueName(spec) + "_ValueList"
	if _, ok := g.setValueLists[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			type <.Name> <typeReference .Spec Required>

			<$v := newVar "v">
			<$x := newVar "x">
			<$f := newVar "f">
			func (<$v> <.Name>) ForEach(<$f> func(<$wire>.Value) error) error {
				for <$x> := range <$v> {
					err := <$f>(<toWire .Spec.ValueSpec $x>)
					if err != nil {
						return err
					}
				}
				return nil
			}

			func (<$v> <.Name>) Close() {}
		`,
		struct {
			Name string
			Spec *compile.SetSpec
		}{Name: name, Spec: spec},
	)
	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.setValueLists[name] = struct{}{}
	return name, nil
}

func (g *Generator) setReader(spec *compile.SetSpec) (string, error) {
	name := "_" + valueName(spec) + "_Read"
	if _, ok := g.setReaders[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			<$setType := typeReference .Spec Required>

			<$s := newVar "s">
			<$i := newVar "i">
			<$o := newVar "o">
			<$x := newVar "x">
			func <.Name>(<$s> <$wire>.Set) <$setType> {
				if <$s>.ValueType != <typeCode .Spec.ValueSpec> {
					return nil
				}

				<$o> := make(<$setType>, <$s>.Size)
				<$s>.Items.ForEach(func(<$x> <$wire>.Value) error {
					var <$i> <typeReference .Spec.ValueSpec Required>
					<fromWire .Spec.ValueSpec $i $x>
					// TODO error handling
					<$o>[<$i>] = struct{}{}
					return nil
				})
				<$s>.Items.Close()
				return <$o>
			}
		`,
		struct {
			Name string
			Spec *compile.SetSpec
		}{Name: name, Spec: spec},
	)

	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.setReaders[name] = struct{}{}
	return name, nil
}

// mapItemList generates a new MapItemList type alias for the given map.
//
// The following is generated:
//
// 	type $mapItemListName map[$keyType]$valueType
//
// 	func (v $mapItemListName) ForEach(f func(wire.MapItem) error) error { ... }
//
// 	func (v $mapItemListName) Close() { ... }
//
// And $mapItemListName is returned. This may be used where a MapItemList of the
// given type is expected.
func (g *Generator) mapItemList(spec *compile.MapSpec) (string, error) {
	// TODO(abg): Unhashable types
	name := "_" + valueName(spec) + "_MapItemList"
	if _, ok := g.mapItemLists[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			type <.Name> <typeReference .Spec Required>

			<$m := newVar "m">
			<$f := newVar "f">
			<$k := newVar "k">
			<$v := newVar "v">
			func (<$m> <.Name>) ForEach(<$f> func(<$wire>.MapItem) error) error {
				for <$k>, <$v> := range <$m> {
					err := <$f>(<$wire>.MapItem{
						Key: <toWire .Spec.KeySpec $k>,
						Value: <toWire .Spec.ValueSpec $v>,
					})
					if err != nil {
						return err
					}
				}
				return nil
			}

			func (<$m> <.Name>) Close() {}
		`,
		struct {
			Name string
			Spec *compile.MapSpec
		}{Name: name, Spec: spec},
	)
	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.mapItemLists[name] = struct{}{}
	return name, nil
}

func (g *Generator) mapReader(spec *compile.MapSpec) (string, error) {
	name := "_" + valueName(spec) + "_Read"
	if _, ok := g.mapReaders[name]; ok {
		return name, nil
	}

	err := g.DeclareFromTemplate(
		`
			<$wire := import "github.com/uber/thriftrw-go/wire">
			<$mapType := typeReference .Spec Required>

			<$m := newVar "m">
			<$o := newVar "o">
			<$x := newVar "x">
			<$k := newVar "k">
			<$v := newVar "v">
			func <.Name>(<$m> <$wire>.Map) <$mapType> {
				if <$m>.KeyType != <typeCode .Spec.KeySpec> {
					return nil
				}

				if <$m>.ValueType != <typeCode .Spec.ValueSpec> {
					return nil
				}

				<$o> := make(<$mapType>, <$m>.Size)
				<$m>.Items.ForEach(func(<$x> <$wire>.MapItem) error {
					var <$k> <typeReference .Spec.KeySpec Required>
					var <$v> <typeReference .Spec.ValueSpec Required>
					<fromWire .Spec.KeySpec $k (printf "%s.Key" $x)>
					<fromWire .Spec.ValueSpec $v (printf "%s.Value" $x)>
					// TODO error handling
					<$o>[<$k>] = <$v>
					return nil
				})
				<$m>.Items.Close()
				return <$o>
			}
		`,
		struct {
			Name string
			Spec *compile.MapSpec
		}{Name: name, Spec: spec},
	)

	if err != nil {
		return "", generateError{
			Name:   typeReference(spec, Required),
			Reason: err,
		}
	}

	g.mapReaders[name] = struct{}{}
	return name, nil
}

func valueName(spec compile.TypeSpec) string {
	switch s := spec.(type) {
	case *compile.MapSpec:
		return fmt.Sprintf(
			"Map_%s_%s", valueName(s.KeySpec), valueName(s.ValueSpec),
		)
	case *compile.ListSpec:
		return fmt.Sprintf("List_%s", valueName(s.ValueSpec))
	case *compile.SetSpec:
		return fmt.Sprintf("Set_%s", valueName(s.ValueSpec))
	default:
		return goCase(spec.ThriftName())
	}
}
<|endoftext|>


<commit_before>// A toolbox to check/generate CloudStack API commands from the JSON description

package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"github.com/exoscale/egoscale"
)

// must be sorted
var ignoredFields = []string{
	"aclid",
	"acltype",
	"forvpc",
	"policyid",
	"project",
	"projectid",
	"vpc",
	"vpcavailable",
	"vpcid",
	"vpclimit",
	"vpctotal",
}

var cmd = flag.String("cmd", "", "CloudStack command name")
var source = flag.String("apis", "", "listApis response in JSON")
var rtype = flag.String("type", "", "Actual type to check against the cmd (need cmd)")

var apiTypes = map[string]string{
	"short":   "int16",
	"integer": "int",
	"long":    "int64",
	"map":     "map[string]string",
	"list":    "[]struct{}",
	"set":     "[]struct{}",
	"uuid":    "string",
	"boolean": "*bool",
	"date":    "string",
}

// fieldInfo represents the inner details of a field
type fieldInfo struct {
	Var       *types.Var
	OmitEmpty bool
	Doc       string
}

// command represents a struct within the source code
type command struct {
	name        string
	description string
	sync        string
	s           *types.Struct
	position    token.Pos
	fields      map[string]fieldInfo
	errors      map[string]error
}

func main() {
	flag.Parse()

	sourceFile, _ := os.Open(*source)
	decoder := json.NewDecoder(sourceFile)
	apis := new(egoscale.ListAPIsResponse)
	if err := decoder.Decode(&apis); err != nil {
		panic(err)
	}

	fset := token.NewFileSet()
	astFiles := make([]*ast.File, 0)
	files, err := filepath.Glob("*.go")
	if err != nil {
		panic(err)
	}
	for _, file := range files {
		f, er := parser.ParseFile(fset, file, nil, 0)
		if er != nil {
			panic(er)
		}
		astFiles = append(astFiles, f)
	}

	info := types.Info{
		Defs: make(map[*ast.Ident]types.Object),
	}

	conf := types.Config{
		Importer: importer.For("source", nil),
	}

	_, err = conf.Check("egoscale", fset, astFiles, &info)
	if err != nil {
		_, e := fmt.Fprintf(os.Stderr, err.Error())
		if e != nil {
			panic(e)
		}
		os.Exit(1)
	}

	commands := make(map[string]*command)

	for id, obj := range info.Defs {
		if obj == nil || !obj.Exported() {
			continue
		}

		typ := obj.Type().Underlying()

		switch typ.(type) {
		case *types.Struct:
			commands[strings.ToLower(obj.Name())] = &command{
				name:     obj.Name(),
				s:        typ.(*types.Struct),
				position: id.Pos(),
			}
		}
	}

	re := regexp.MustCompile(`\bjson:"(?P<name>[^,"]+)(?P<omit>,omitempty)?"`)
	reDoc := regexp.MustCompile(`\bdoc:"(?P<doc>[^"]+)"`)

	for _, a := range apis.API {
		name := strings.ToLower(a.Name)
		params := a.Params

		if strings.ToLower(*cmd) == name && *rtype != "" {
			name = strings.ToLower(*rtype)
			*cmd = name
			params = a.Response
			_, e := fmt.Fprintf(os.Stderr, "Checking return type of %sResult, using %q\n", a.Name, *rtype)
			if e != nil {
				panic(e)
			}
		}

		if command, ok := commands[name]; !ok {
			// too much information
			//fmt.Fprintf(os.Stderr, "Unknown command: %q\n", name)
		} else {
			command.description = strings.Trim(a.Description, " ")
			// mapping from name to field
			command.fields = make(map[string]fieldInfo)
			command.errors = make(map[string]error)

			if a.IsAsync {
				command.sync = " (A)"
			}

			hasMeta := false

			for i := 0; i < command.s.NumFields(); i++ {
				f := command.s.Field(i)

				if !f.IsField() || !f.Exported() {
					if f.Name() != "_" {
						continue
					}

					tag := (reflect.StructTag)(command.s.Tag(i))
					name, nameOK := tag.Lookup("name")
					description, descriptionOK := tag.Lookup("description")
					if !nameOK || !descriptionOK {
						command.errors["_"] = fmt.Errorf("meta field incomplete, wanted\n\t_ bool `name:%q description:%q`", a.Name, command.description)
					} else {
						if name != a.Name || description != command.description {
							command.errors["_"] = fmt.Errorf("meta field incorrect, got %q %q, wanted\n\t\t_ bool `name:%q description:%q`", name, description, a.Name, command.description)
						}
					}

					hasMeta = true
					continue
				}

				tag := command.s.Tag(i)
				match := re.FindStringSubmatch(tag)
				if len(match) == 0 {
					command.errors[f.Name()] = errors.New("field error: no json annotation found")
					continue
				}
				name := match[1]
				omitempty := len(match) == 3 && match[2] == ",omitempty"

				doc := ""
				match = reDoc.FindStringSubmatch(tag)
				if len(match) == 2 {
					doc = match[1]
				}

				command.fields[name] = fieldInfo{
					Var:       f,
					OmitEmpty: omitempty,
					Doc:       doc,
				}
			}

			for _, p := range params {
				index := sort.SearchStrings(ignoredFields, p.Name)
				ignored := index < len(ignoredFields) && ignoredFields[index] == p.Name
				if ignored {
					continue
				}
				field, ok := command.fields[p.Name]
				description := strings.Trim(p.Description, " ")

				omit := ""
				if !p.Required {
					omit = ",omitempty"
				}

				if !ok {
					doc := ""
					if description != "" {
						doc = fmt.Sprintf(" doc:%q", description)
					}

					apiType, ok := apiTypes[p.Type]
					if !ok {
						apiType = p.Type
					}

					command.errors[p.Name] = fmt.Errorf("missing field:\n\t%s %s `json:\"%s%s\"%s`", strings.Title(p.Name), apiType, p.Name, omit, doc)
					continue
				}
				delete(command.fields, p.Name)

				typename := field.Var.Type().String()

				if field.Doc != description {
					if field.Doc == "" {
						command.errors[p.Name] = fmt.Errorf("missing doc:\n\t\t`doc:%q`", description)
					} else {
						command.errors[p.Name] = fmt.Errorf("wrong doc want %q got %q", description, field.Doc)
					}
				}

				if p.Required == field.OmitEmpty {
					command.errors[p.Name] = fmt.Errorf("wrong omitempty, want `json:\"%s%s\"`", p.Name, omit)
					continue
				}

				expected := ""
				switch p.Type {
				case "short":
					if typename != "int16" {
						expected = "int16"
					}
				case "int":
				case "integer":
					// uint are used by port and icmp types
					if typename != "int" && typename != "uint16" && typename != "uint8" {
						expected = "int"
					}
					// skip enums
					if typename == "egoscale.ResourceType" {
						expected = ""
					}
				case "long":
					if typename != "int64" && typename != "uint64" {
						expected = "int64"
					}
				case "boolean":
					if typename != "bool" && typename != "*bool" {
						expected = "bool"
					}
				case "string":
				case "uuid":
				case "date":
				case "tzdate":
				case "imageformat":
					if typename != "string" {
						expected = "string"
					}
				case "list":
					if !strings.HasPrefix(typename, "[]") {
						expected = "[]string"
					}
				case "map":
				case "set":
					if !strings.HasPrefix(typename, "[]") {
						expected = "array"
					}
				default:
					command.errors[p.Name] = fmt.Errorf("unknown type %q <=> %q", p.Type, field.Var.Type().String())
				}

				if expected != "" {
					command.errors[p.Name] = fmt.Errorf("expected to be a %s, got %q", expected, typename)
				}
			}

			if !hasMeta && *rtype == "" {
				command.errors["_"] = fmt.Errorf("meta field missing, wanted\n\t\t_ bool `name:%q description:%q`", a.Name, a.Description)
			}

			for name := range command.fields {
				command.errors[name] = errors.New("extra field found")
			}
		}
	}

	for name, c := range commands {
		pos := fset.Position(c.position)
		er := len(c.errors)

		if *cmd == "" {
			if er != 0 {
				fmt.Printf("%5d %s: %s%s\n", er, pos, c.name, c.sync)
			}
		} else if strings.ToLower(*cmd) == name {
			errs := make([]string, 0, len(c.errors))
			for k, e := range c.errors {
				errs = append(errs, fmt.Sprintf("%s: %s", k, e.Error()))
			}
			sort.Strings(errs)
			for _, e := range errs {
				fmt.Println(e)
			}
			fmt.Printf("\n%s: %s%s has %d error(s)\n", pos, c.name, c.sync, er)
			os.Exit(er)
		}
	}

	if *cmd != "" {
		fmt.Printf("%s not found\n", *cmd)
		os.Exit(1)
	}
}
<commit_msg>generate: style fixes (#250)<commit_after>// A toolbox to check/generate CloudStack API commands from the JSON description

package main

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/token"
	"go/types"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"github.com/exoscale/egoscale"
)

// must be sorted
var ignoredFields = []string{
	"aclid",
	"acltype",
	"forvpc",
	"policyid",
	"project",
	"projectid",
	"vpc",
	"vpcavailable",
	"vpcid",
	"vpclimit",
	"vpctotal",
}

var cmd = flag.String("cmd", "", "CloudStack command name")
var source = flag.String("apis", "", "listApis response in JSON")
var rtype = flag.String("type", "", "Actual type to check against the cmd (need cmd)")

var apiTypes = map[string]string{
	"short":   "int16",
	"integer": "int",
	"long":    "int64",
	"map":     "map[string]string",
	"list":    "[]struct{}",
	"set":     "[]struct{}",
	"uuid":    "string",
	"boolean": "*bool",
	"date":    "string",
}

// fieldInfo represents the inner details of a field
type fieldInfo struct {
	Var       *types.Var
	OmitEmpty bool
	Doc       string
}

// command represents a struct within the source code
type command struct {
	name        string
	description string
	sync        string
	s           *types.Struct
	position    token.Pos
	fields      map[string]fieldInfo
	errors      map[string]error
}

func main() {
	flag.Parse()

	sourceFile, _ := os.Open(*source)
	decoder := json.NewDecoder(sourceFile)
	apis := new(egoscale.ListAPIsResponse)
	if err := decoder.Decode(&apis); err != nil {
		panic(err)
	}

	files, err := filepath.Glob("*.go")
	if err != nil {
		panic(err)
	}
	fset := token.NewFileSet()
	astFiles := make([]*ast.File, len(files))
	for i, file := range files {
		f, er := parser.ParseFile(fset, file, nil, 0)
		if er != nil {
			panic(er)
		}
		astFiles[i] = f
	}

	config := types.Config{
		Importer: importer.For("source", nil),
	}

	info := &types.Info{
		Defs: make(map[*ast.Ident]types.Object),
	}

	_, err = config.Check("egoscale", fset, astFiles, info)
	if err != nil {
		_, e := fmt.Fprintf(os.Stderr, err.Error())
		if e != nil {
			panic(e)
		}
		os.Exit(1)
	}

	commands := make(map[string]*command)

	for id, obj := range info.Defs {
		if obj == nil || !obj.Exported() {
			continue
		}

		typ := obj.Type().Underlying()

		switch typ.(type) {
		case *types.Struct:
			commands[strings.ToLower(obj.Name())] = &command{
				name:     obj.Name(),
				s:        typ.(*types.Struct),
				position: id.Pos(),
			}
		}
	}

	re := regexp.MustCompile(`\bjson:"(?P<name>[^,"]+)(?P<omit>,omitempty)?"`)
	reDoc := regexp.MustCompile(`\bdoc:"(?P<doc>[^"]+)"`)

	for _, a := range apis.API {
		name := strings.ToLower(a.Name)
		params := a.Params

		if strings.ToLower(*cmd) == name && *rtype != "" {
			name = strings.ToLower(*rtype)
			*cmd = name
			params = a.Response
			_, e := fmt.Fprintf(os.Stderr, "Checking return type of %sResult, using %q\n", a.Name, *rtype)
			if e != nil {
				panic(e)
			}
		}

		if command, ok := commands[name]; !ok {
			// too much information
			//fmt.Fprintf(os.Stderr, "Unknown command: %q\n", name)
		} else {
			command.description = strings.Trim(a.Description, " ")
			// mapping from name to field
			command.fields = make(map[string]fieldInfo)
			command.errors = make(map[string]error)

			if a.IsAsync {
				command.sync = " (A)"
			}

			hasMeta := false

			for i := 0; i < command.s.NumFields(); i++ {
				f := command.s.Field(i)

				if !f.IsField() || !f.Exported() {
					if f.Name() != "_" {
						continue
					}

					tag := (reflect.StructTag)(command.s.Tag(i))
					name, nameOK := tag.Lookup("name")
					description, descriptionOK := tag.Lookup("description")
					if !nameOK || !descriptionOK {
						command.errors["_"] = fmt.Errorf("meta field incomplete, wanted\n\t_ bool `name:%q description:%q`", a.Name, command.description)
					} else {
						if name != a.Name || description != command.description {
							command.errors["_"] = fmt.Errorf("meta field incorrect, got %q %q, wanted\n\t\t_ bool `name:%q description:%q`", name, description, a.Name, command.description)
						}
					}

					hasMeta = true
					continue
				}

				tag := command.s.Tag(i)
				match := re.FindStringSubmatch(tag)
				if len(match) == 0 {
					command.errors[f.Name()] = errors.New("field error: no json annotation found")
					continue
				}
				name := match[1]
				omitempty := len(match) == 3 && match[2] == ",omitempty"

				doc := ""
				match = reDoc.FindStringSubmatch(tag)
				if len(match) == 2 {
					doc = match[1]
				}

				command.fields[name] = fieldInfo{
					Var:       f,
					OmitEmpty: omitempty,
					Doc:       doc,
				}
			}

			for _, p := range params {
				index := sort.SearchStrings(ignoredFields, p.Name)
				ignored := index < len(ignoredFields) && ignoredFields[index] == p.Name
				if ignored {
					continue
				}
				field, ok := command.fields[p.Name]
				description := strings.Trim(p.Description, " ")

				omit := ""
				if !p.Required {
					omit = ",omitempty"
				}

				if !ok {
					doc := ""
					if description != "" {
						doc = fmt.Sprintf(" doc:%q", description)
					}

					apiType, ok := apiTypes[p.Type]
					if !ok {
						apiType = p.Type
					}

					command.errors[p.Name] = fmt.Errorf("missing field:\n\t%s %s `json:\"%s%s\"%s`", strings.Title(p.Name), apiType, p.Name, omit, doc)
					continue
				}
				delete(command.fields, p.Name)

				typename := field.Var.Type().String()

				if field.Doc != description {
					if field.Doc == "" {
						command.errors[p.Name] = fmt.Errorf("missing doc:\n\t\t`doc:%q`", description)
					} else {
						command.errors[p.Name] = fmt.Errorf("wrong doc want %q got %q", description, field.Doc)
					}
				}

				if p.Required == field.OmitEmpty {
					command.errors[p.Name] = fmt.Errorf("wrong omitempty, want `json:\"%s%s\"`", p.Name, omit)
					continue
				}

				expected := ""
				switch p.Type {
				case "short":
					if typename != "int16" {
						expected = "int16"
					}
				case "int":
				case "integer":
					// uint are used by port and icmp types
					if typename != "int" && typename != "uint16" && typename != "uint8" {
						expected = "int"
					}
					// skip enums
					if typename == "egoscale.ResourceType" {
						expected = ""
					}
				case "long":
					if typename != "int64" && typename != "uint64" {
						expected = "int64"
					}
				case "boolean":
					if typename != "bool" && typename != "*bool" {
						expected = "bool"
					}
				case "string":
				case "uuid":
				case "date":
				case "tzdate":
				case "imageformat":
					if typename != "string" {
						expected = "string"
					}
				case "list":
					if !strings.HasPrefix(typename, "[]") {
						expected = "[]string"
					}
				case "map":
				case "set":
					if !strings.HasPrefix(typename, "[]") {
						expected = "array"
					}
				default:
					command.errors[p.Name] = fmt.Errorf("unknown type %q <=> %q", p.Type, field.Var.Type().String())
				}

				if expected != "" {
					command.errors[p.Name] = fmt.Errorf("expected to be a %s, got %q", expected, typename)
				}
			}

			if !hasMeta && *rtype == "" {
				command.errors["_"] = fmt.Errorf("meta field missing, wanted\n\t\t_ bool `name:%q description:%q`", a.Name, a.Description)
			}

			for name := range command.fields {
				command.errors[name] = errors.New("extra field found")
			}
		}
	}

	for name, c := range commands {
		pos := fset.Position(c.position)
		er := len(c.errors)

		if *cmd == "" {
			if er != 0 {
				fmt.Printf("%5d %s: %s%s\n", er, pos, c.name, c.sync)
			}
		} else if strings.ToLower(*cmd) == name {
			errs := make([]string, 0, len(c.errors))
			for k, e := range c.errors {
				errs = append(errs, fmt.Sprintf("%s: %s", k, e.Error()))
			}
			sort.Strings(errs)
			for _, e := range errs {
				fmt.Println(e)
			}
			fmt.Printf("\n%s: %s%s has %d error(s)\n", pos, c.name, c.sync, er)
			os.Exit(er)
		}
	}

	if *cmd != "" {
		fmt.Printf("%s not found\n", *cmd)
		os.Exit(1)
	}
}
<|endoftext|>


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

package inode

import (
	"fmt"
	"os"
	"path"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

type DirInode struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	id fuseops.InodeID

	// The the GCS object backing the inode. The object's name is used as a
	// prefix when listing. Special case: the empty string means this is the root
	// inode.
	//
	// INVARIANT: src != nil
	// INVARIANT: src.Name == "" || src.Name[len(name)-1] == '/'
	src storage.Object
}

var _ Inode = &DirInode{}

// Create a directory inode for the root of the file system. For this inode,
// the result of SourceGeneration() is guaranteed to be zero.
func NewRootInode(bucket gcs.Bucket) (d *DirInode) {
	d = &DirInode{
		bucket: bucket,
		id:     fuseops.RootInodeID,

		// A dummy object whose name is the empty string.
		src: storage.Object{},
	}

	return
}

// Create a directory inode for the supplied source object. The object's name
// must end with a slash unless this is the root directory, in which case it
// must be empty.
//
// REQUIRES: o != nil
// REQUIRES: o.Name != ""
// REQUIRES: o.Name[len(o.Name)-1] == '/'
func NewDirInode(
	bucket gcs.Bucket,
	id fuseops.InodeID,
	o *storage.Object) (d *DirInode) {
	if o.Name[len(o.Name)-1] != '/' {
		panic(fmt.Sprintf("Unexpected name: %s", o.Name))
	}

	// Set up the struct.
	d = &DirInode{
		bucket: bucket,
		id:     id,
		src:    *o,
	}

	return
}

// See notes on NewImplicitDirInode.
const ImplicitDirGen int64 = -1

// Create a directory inode for the supplied name, with the understanding that
// this is an "implicit" directory inode not backed by an actual source object.
// For this inode, the result of SourceGeneration() is guaranteed to be
// ImplicitDirGen.
//
// REQUIRES: name != ""
// REQUIRES: name[len(o.Name)-1] == '/'
func NewImplicitDirInode(
	bucket gcs.Bucket,
	id fuseops.InodeID,
	name string) (d *DirInode) {
	dummy := &storage.Object{
		Name:       name,
		Generation: ImplicitDirGen,
	}

	d = NewDirInode(bucket, id, dummy)
	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func (d *DirInode) checkInvariants() {
	// INVARIANT: src.Name == "" || src.Name[len(name)-1] == '/'
	if !(d.src.Name == "" || d.src.Name[len(d.src.Name)-1] == '/') {
		panic(fmt.Sprintf("Unexpected name: %s", d.src.Name))
	}
}

func (d *DirInode) clobbered(ctx context.Context) (clobbered bool, err error) {
	// Special case: the root is never clobbered.
	if d.ID() == fuseops.RootInodeID {
		return
	}

	// Stat the backing object.
	req := &gcs.StatObjectRequest{
		Name: d.Name(),
	}

	o, err := d.bucket.StatObject(ctx, req)

	// "Not found" means clobbered.
	if _, ok := err.(*gcs.NotFoundError); ok {
		clobbered = true
		err = nil
		return
	}

	// Propagate other errors.
	if err != nil {
		err = fmt.Errorf("StatObject: %v", err)
		return
	}

	// We are clobbered if the generation number has changed.
	clobbered = o.Generation != d.SourceGeneration()

	return
}

// Stat the object with the given name, returning (nil, nil) if the object
// doesn't exist rather than failing.
func statObjectMayNotExist(
	ctx context.Context,
	bucket gcs.Bucket,
	name string) (o *storage.Object, err error) {
	// Call the bucket.
	req := &gcs.StatObjectRequest{
		Name: name,
	}

	o, err = bucket.StatObject(ctx, req)

	// Suppress "not found" errors.
	if _, ok := err.(*gcs.NotFoundError); ok {
		err = nil
	}

	// Annotate others.
	if err != nil {
		err = fmt.Errorf("StatObject: %v", err)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (d *DirInode) Lock() {
	// We don't require locks.
}

func (d *DirInode) Unlock() {
	// We don't require locks.
}

// Return the ID of this inode.
func (d *DirInode) ID() fuseops.InodeID {
	return d.id
}

// Return the full name of the directory object in GCS, including the trailing
// slash (e.g. "foo/bar/").
func (d *DirInode) Name() string {
	return d.src.Name
}

// Return the generation number from which this inode was branched.
func (d *DirInode) SourceGeneration() int64 {
	return d.src.Generation
}

func (d *DirInode) Attributes(
	ctx context.Context) (attrs fuseops.InodeAttributes, err error) {
	// Find out whether the backing object has been clobbered in GCS.
	clobbered, err := d.clobbered(ctx)
	if err != nil {
		err = fmt.Errorf("clobbered: %v", err)
		return
	}

	// Set up basic attributes.
	attrs = fuseops.InodeAttributes{
		Mode: 0700 | os.ModeDir,
	}

	// Modify Nlink as appropriate.
	if !clobbered {
		attrs.Nlink = 1
	}

	return
}

// Look up the direct child with the given relative name, returning a record
// for the current object of that name in the GCS bucket. If both a file and a
// directory with the given name exist, be consistent from call to call about
// which is preferred. Return fuse.ENOENT if neither is found.
func (d *DirInode) LookUpChild(
	ctx context.Context,
	name string) (o *storage.Object, err error) {
	b := syncutil.NewBundle(ctx)

	// Stat the child as a file.
	var fileRecord *storage.Object
	b.Add(func(ctx context.Context) (err error) {
		fileRecord, err = statObjectMayNotExist(ctx, d.bucket, d.Name()+name)
		return
	})

	// Stat the child as a directory.
	var dirRecord *storage.Object
	b.Add(func(ctx context.Context) (err error) {
		dirRecord, err = statObjectMayNotExist(ctx, d.bucket, d.Name()+name+"/")
		return
	})

	// Wait for both.
	err = b.Join()
	if err != nil {
		return
	}

	// Prefer directories over files.
	switch {
	case dirRecord != nil:
		o = dirRecord
	case fileRecord != nil:
		o = fileRecord
	default:
		err = fuse.ENOENT
	}

	return
}

// Read some number of entries from the directory, returning a continuation
// token that can be used to pick up the read operation where it left off.
// Supply the empty token on the first call.
//
// At the end of the directory, the returned continuation token will be empty.
// Otherwise it will be non-empty. There is no guarantee about the number of
// entries returned; it may be zero even with a non-empty continuation token.
//
// The contents of the Offset and Inode fields for returned entries is
// undefined.
func (d *DirInode) ReadEntries(
	ctx context.Context,
	tok string) (entries []fuseutil.Dirent, newTok string, err error) {
	// Ask the bucket to list some objects.
	query := &storage.Query{
		Delimiter: "/",
		Prefix:    d.Name(),
		Cursor:    tok,
	}

	listing, err := d.bucket.ListObjects(ctx, query)
	if err != nil {
		err = fmt.Errorf("ListObjects: %v", err)
		return
	}

	// Convert objects to entries for files.
	for _, o := range listing.Results {
		e := fuseutil.Dirent{
			Name: path.Base(o.Name),
			Type: fuseutil.DT_File,
		}

		entries = append(entries, e)
	}

	// Convert prefixes to entries for directories.
	for _, p := range listing.Prefixes {
		e := fuseutil.Dirent{
			Name: path.Base(p),
			Type: fuseutil.DT_Directory,
		}

		entries = append(entries, e)
	}

	// Return an appropriate continuation token, if any.
	if listing.Next != nil {
		newTok = listing.Next.Cursor
	}

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

package inode

import (
	"fmt"
	"os"
	"path"

	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseops"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

type DirInode struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	bucket gcs.Bucket

	/////////////////////////
	// Constant data
	/////////////////////////

	id fuseops.InodeID

	// The the GCS object backing the inode. The object's name is used as a
	// prefix when listing. Special case: the empty string means this is the root
	// inode.
	//
	// INVARIANT: src != nil
	// INVARIANT: src.Name == "" || src.Name[len(name)-1] == '/'
	src storage.Object
}

var _ Inode = &DirInode{}

// Create a directory inode for the root of the file system. For this inode,
// the result of SourceGeneration() is guaranteed to be zero.
func NewRootInode(bucket gcs.Bucket) (d *DirInode) {
	d = &DirInode{
		bucket: bucket,
		id:     fuseops.RootInodeID,

		// A dummy object whose name is the empty string.
		src: storage.Object{},
	}

	return
}

// Create a directory inode for the supplied source object. The object's name
// must end with a slash unless this is the root directory, in which case it
// must be empty.
//
// REQUIRES: o != nil
// REQUIRES: o.Name != ""
// REQUIRES: o.Name[len(o.Name)-1] == '/'
func NewDirInode(
	bucket gcs.Bucket,
	id fuseops.InodeID,
	o *storage.Object) (d *DirInode) {
	if o.Name[len(o.Name)-1] != '/' {
		panic(fmt.Sprintf("Unexpected name: %s", o.Name))
	}

	// Set up the struct.
	d = &DirInode{
		bucket: bucket,
		id:     id,
		src:    *o,
	}

	return
}

// See notes on NewImplicitDirInode.
const ImplicitDirGen int64 = -1

// Like NewDirInode, but with the understanding that this is an "implicit"
// directory inode not backed by an actual source object. For this inode, the
// result of SourceGeneration() is guaranteed to be ImplicitDirGen.
func NewImplicitDirInode(
	bucket gcs.Bucket,
	id fuseops.InodeID,
	name string) (d *DirInode) {
	dummy := &storage.Object{
		Name:       name,
		Generation: ImplicitDirGen,
	}

	d = NewDirInode(bucket, id, dummy)
	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func (d *DirInode) checkInvariants() {
	// INVARIANT: src.Name == "" || src.Name[len(name)-1] == '/'
	if !(d.src.Name == "" || d.src.Name[len(d.src.Name)-1] == '/') {
		panic(fmt.Sprintf("Unexpected name: %s", d.src.Name))
	}
}

func (d *DirInode) clobbered(ctx context.Context) (clobbered bool, err error) {
	// Special case: the root is never clobbered.
	if d.ID() == fuseops.RootInodeID {
		return
	}

	// Stat the backing object.
	req := &gcs.StatObjectRequest{
		Name: d.Name(),
	}

	o, err := d.bucket.StatObject(ctx, req)

	// "Not found" means clobbered.
	if _, ok := err.(*gcs.NotFoundError); ok {
		clobbered = true
		err = nil
		return
	}

	// Propagate other errors.
	if err != nil {
		err = fmt.Errorf("StatObject: %v", err)
		return
	}

	// We are clobbered if the generation number has changed.
	clobbered = o.Generation != d.SourceGeneration()

	return
}

// Stat the object with the given name, returning (nil, nil) if the object
// doesn't exist rather than failing.
func statObjectMayNotExist(
	ctx context.Context,
	bucket gcs.Bucket,
	name string) (o *storage.Object, err error) {
	// Call the bucket.
	req := &gcs.StatObjectRequest{
		Name: name,
	}

	o, err = bucket.StatObject(ctx, req)

	// Suppress "not found" errors.
	if _, ok := err.(*gcs.NotFoundError); ok {
		err = nil
	}

	// Annotate others.
	if err != nil {
		err = fmt.Errorf("StatObject: %v", err)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (d *DirInode) Lock() {
	// We don't require locks.
}

func (d *DirInode) Unlock() {
	// We don't require locks.
}

// Return the ID of this inode.
func (d *DirInode) ID() fuseops.InodeID {
	return d.id
}

// Return the full name of the directory object in GCS, including the trailing
// slash (e.g. "foo/bar/").
func (d *DirInode) Name() string {
	return d.src.Name
}

// Return the generation number from which this inode was branched.
func (d *DirInode) SourceGeneration() int64 {
	return d.src.Generation
}

func (d *DirInode) Attributes(
	ctx context.Context) (attrs fuseops.InodeAttributes, err error) {
	// Find out whether the backing object has been clobbered in GCS.
	clobbered, err := d.clobbered(ctx)
	if err != nil {
		err = fmt.Errorf("clobbered: %v", err)
		return
	}

	// Set up basic attributes.
	attrs = fuseops.InodeAttributes{
		Mode: 0700 | os.ModeDir,
	}

	// Modify Nlink as appropriate.
	if !clobbered {
		attrs.Nlink = 1
	}

	return
}

// Look up the direct child with the given relative name, returning a record
// for the current object of that name in the GCS bucket. If both a file and a
// directory with the given name exist, be consistent from call to call about
// which is preferred. Return fuse.ENOENT if neither is found.
func (d *DirInode) LookUpChild(
	ctx context.Context,
	name string) (o *storage.Object, err error) {
	b := syncutil.NewBundle(ctx)

	// Stat the child as a file.
	var fileRecord *storage.Object
	b.Add(func(ctx context.Context) (err error) {
		fileRecord, err = statObjectMayNotExist(ctx, d.bucket, d.Name()+name)
		return
	})

	// Stat the child as a directory.
	var dirRecord *storage.Object
	b.Add(func(ctx context.Context) (err error) {
		dirRecord, err = statObjectMayNotExist(ctx, d.bucket, d.Name()+name+"/")
		return
	})

	// Wait for both.
	err = b.Join()
	if err != nil {
		return
	}

	// Prefer directories over files.
	switch {
	case dirRecord != nil:
		o = dirRecord
	case fileRecord != nil:
		o = fileRecord
	default:
		err = fuse.ENOENT
	}

	return
}

// Read some number of entries from the directory, returning a continuation
// token that can be used to pick up the read operation where it left off.
// Supply the empty token on the first call.
//
// At the end of the directory, the returned continuation token will be empty.
// Otherwise it will be non-empty. There is no guarantee about the number of
// entries returned; it may be zero even with a non-empty continuation token.
//
// The contents of the Offset and Inode fields for returned entries is
// undefined.
func (d *DirInode) ReadEntries(
	ctx context.Context,
	tok string) (entries []fuseutil.Dirent, newTok string, err error) {
	// Ask the bucket to list some objects.
	query := &storage.Query{
		Delimiter: "/",
		Prefix:    d.Name(),
		Cursor:    tok,
	}

	listing, err := d.bucket.ListObjects(ctx, query)
	if err != nil {
		err = fmt.Errorf("ListObjects: %v", err)
		return
	}

	// Convert objects to entries for files.
	for _, o := range listing.Results {
		e := fuseutil.Dirent{
			Name: path.Base(o.Name),
			Type: fuseutil.DT_File,
		}

		entries = append(entries, e)
	}

	// Convert prefixes to entries for directories.
	for _, p := range listing.Prefixes {
		e := fuseutil.Dirent{
			Name: path.Base(p),
			Type: fuseutil.DT_Directory,
		}

		entries = append(entries, e)
	}

	// Return an appropriate continuation token, if any.
	if listing.Next != nil {
		newTok = listing.Next.Cursor
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
    "net/http"
    "io/ioutil"
    "strings"
    "testing"
    "time"
)

func curl(url string) string {
    if r, err := http.Get("http://localhost:8080/" + url); err == nil {
        b, err := ioutil.ReadAll(r.Body)
        r.Body.Close()
        if err == nil {
            return string(b)
        }
    }
    return""
}

func mustContain(t *testing.T, page string, what string) {
    if !strings.Contains(page, what) {
        t.Errorf("Test page did not contain %q", what)
    }
}

var simpleTests = []struct {
    url string
    out string
}{
    {"", "container"},
    {"", "header"},
    {"", "subheader"},
    {"", "content"},
    {"", "sidebar"},
    {"", "footer"},
    {"", "blueprint"},
    {"", "utf-8"},
    {"", "gopher.png"},
    {"", "vim_created.png"},
}

func TestMainPage(t *testing.T) {
    go main()
    time.Sleep(50 * time.Millisecond)
    for _, test := range simpleTests {
        mustContain(t, curl(test.url), test.out)
    }
}
<commit_msg>gofmt<commit_after>package main

import (
    "io/ioutil"
    "net/http"
    "strings"
    "testing"
    "time"
)

func curl(url string) string {
    if r, err := http.Get("http://localhost:8080/" + url); err == nil {
        b, err := ioutil.ReadAll(r.Body)
        r.Body.Close()
        if err == nil {
            return string(b)
        }
    }
    return ""
}

func mustContain(t *testing.T, page string, what string) {
    if !strings.Contains(page, what) {
        t.Errorf("Test page did not contain %q", what)
    }
}

var simpleTests = []struct {
    url string
    out string
}{
    {"", "container"},
    {"", "header"},
    {"", "subheader"},
    {"", "content"},
    {"", "sidebar"},
    {"", "footer"},
    {"", "blueprint"},
    {"", "utf-8"},
    {"", "gopher.png"},
    {"", "vim_created.png"},
}

func TestMainPage(t *testing.T) {
    go main()
    time.Sleep(50 * time.Millisecond)
    for _, test := range simpleTests {
        mustContain(t, curl(test.url), test.out)
    }
}
<|endoftext|>


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

package main

var cmdDestroyUnit = &Command{
	Name:    "destroy",
	Summary: "Destroy one or more units in the cluster",
	Usage:   "UNIT...",
	Description: `Completely remove one or more running or submitted units from the cluster.

Instructs systemd on the host machine to stop the unit, deferring to systemd
completely for any custom stop directives (i.e. ExecStop option in the unit
file).

Destroyed units are impossible to start unless re-submitted.`,
	Run: runDestroyUnits,
}

func runDestroyUnits(args []string) (exit int) {
	for _, v := range args {
		name := unitNameMangle(v)
		err := cAPI.DestroyUnit(name)
		if err != nil {
			continue
		}

		stdout("Destroyed %s", name)
	}
	return
}
<commit_msg>fleetctl: destroy using wildcards can search in the repository<commit_after>// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"path"
)

var cmdDestroyUnit = &Command{
	Name:    "destroy",
	Summary: "Destroy one or more units in the cluster",
	Usage:   "UNIT...",
	Description: `Completely remove one or more running or submitted units from the cluster.

Instructs systemd on the host machine to stop the unit, deferring to systemd
completely for any custom stop directives (i.e. ExecStop option in the unit
file).

Destroyed units are impossible to start unless re-submitted.`,
	Run: runDestroyUnits,
}

func runDestroyUnits(args []string) (exit int) {
	states, err := cAPI.UnitStates()
	if err != nil {
		stderr("Error retrieving list of units from repository: %v", err)
		return 1
	}

	for _, v := range args {
		name := unitNameMangle(v)
		for _, us := range states {
			if match, _ := path.Match(name, us.Name); match == false {
				continue
			}

			err = cAPI.DestroyUnit(us.Name)
			if err != nil {
				stderr("Error destroying unit %s: %v", us.Name, err)
				continue
			}

			stdout("Destroyed %s", us.Name)
		}
	}
	return
}
<|endoftext|>


<commit_before>package flow

import (
	"os"
	"sync"
)

/*
There are 3 running mode:
1. as normal program
	If not in distributed mode, it should not be intercepted.
2. "-driver" mode to drive in distributed mode
	context runner will register
3. "-task.[context|taskGroup].id" mode to run task in distributed mode
*/

var contextRunner ContextRunner
var taskRunner TaskRunner

// Invoked by driver task runner
func RegisterContextRunner(r ContextRunner) {
	contextRunner = r
}
func RegisterTaskRunner(r TaskRunner) {
	taskRunner = r
}

type ContextRunner interface {
	Run(*FlowContext)
	IsDriverMode() bool
	IsDriverPlotMode() bool
	Plot(*FlowContext)
}

type TaskRunner interface {
	Run(fc *FlowContext)
	IsTaskMode() bool
}

func Ready() {
	if taskRunner != nil && taskRunner.IsTaskMode() {
		for _, fc := range Contexts {
			fc.Run()
		}
		os.Exit(0)
	} else if contextRunner != nil && contextRunner.IsDriverMode() {
		if contextRunner.IsDriverPlotMode() {
			for _, fc := range Contexts {
				contextRunner.Plot(fc)
			}
			os.Exit(0)
		}
	} else {
	}
}

func (fc *FlowContext) Run() {

	if taskRunner != nil && taskRunner.IsTaskMode() {
		taskRunner.Run(fc)
	} else if contextRunner != nil && contextRunner.IsDriverMode() {
		contextRunner.Run(fc)
	} else {
		fc.runFlowContextInStandAloneMode()
	}
}

func (fc *FlowContext) runFlowContextInStandAloneMode() {

	var wg sync.WaitGroup

	isDatasetStarted := make(map[int]bool)

	OnInterrupt(fc.OnInterrupt, nil)

	// start all task edges
	for _, step := range fc.Steps {
		for _, input := range step.Inputs {
			if _, ok := isDatasetStarted[input.Id]; !ok {
				wg.Add(1)
				go func(step *Step) {
					defer wg.Done()
					input.RunDatasetInStandAloneMode()
				}(step)
				isDatasetStarted[input.Id] = true
			}
		}
		wg.Add(1)
		go func(step *Step) {
			defer wg.Done()
			step.RunStep()
		}(step)

		if step.Output != nil {
			if _, ok := isDatasetStarted[step.Output.Id]; !ok {
				wg.Add(1)
				go func(step *Step) {
					defer wg.Done()
					// println(step.Name, "start output dataset", step.Output.Id)
					step.Output.RunDatasetInStandAloneMode()
				}(step)
				isDatasetStarted[step.Output.Id] = true
			}
		}
	}
	wg.Wait()
}
<commit_msg>fix possible multiple inputs error<commit_after>package flow

import (
	"os"
	"sync"
)

/*
There are 3 running mode:
1. as normal program
	If not in distributed mode, it should not be intercepted.
2. "-driver" mode to drive in distributed mode
	context runner will register
3. "-task.[context|taskGroup].id" mode to run task in distributed mode
*/

var contextRunner ContextRunner
var taskRunner TaskRunner

// Invoked by driver task runner
func RegisterContextRunner(r ContextRunner) {
	contextRunner = r
}
func RegisterTaskRunner(r TaskRunner) {
	taskRunner = r
}

type ContextRunner interface {
	Run(*FlowContext)
	IsDriverMode() bool
	IsDriverPlotMode() bool
	Plot(*FlowContext)
}

type TaskRunner interface {
	Run(fc *FlowContext)
	IsTaskMode() bool
}

func Ready() {
	if taskRunner != nil && taskRunner.IsTaskMode() {
		for _, fc := range Contexts {
			fc.Run()
		}
		os.Exit(0)
	} else if contextRunner != nil && contextRunner.IsDriverMode() {
		if contextRunner.IsDriverPlotMode() {
			for _, fc := range Contexts {
				contextRunner.Plot(fc)
			}
			os.Exit(0)
		}
	} else {
	}
}

func (fc *FlowContext) Run() {

	if taskRunner != nil && taskRunner.IsTaskMode() {
		taskRunner.Run(fc)
	} else if contextRunner != nil && contextRunner.IsDriverMode() {
		contextRunner.Run(fc)
	} else {
		fc.runFlowContextInStandAloneMode()
	}
}

func (fc *FlowContext) runFlowContextInStandAloneMode() {

	var wg sync.WaitGroup

	isDatasetStarted := make(map[int]bool)

	OnInterrupt(fc.OnInterrupt, nil)

	// start all task edges
	for _, step := range fc.Steps {
		for _, input := range step.Inputs {
			if _, ok := isDatasetStarted[input.Id]; !ok {
				wg.Add(1)
				go func(input *Dataset, step *Step) {
					defer wg.Done()
					input.RunDatasetInStandAloneMode()
				}(input, step)
				isDatasetStarted[input.Id] = true
			}
		}
		wg.Add(1)
		go func(step *Step) {
			defer wg.Done()
			step.RunStep()
		}(step)

		if step.Output != nil {
			if _, ok := isDatasetStarted[step.Output.Id]; !ok {
				wg.Add(1)
				go func(step *Step) {
					defer wg.Done()
					// println(step.Name, "start output dataset", step.Output.Id)
					step.Output.RunDatasetInStandAloneMode()
				}(step)
				isDatasetStarted[step.Output.Id] = true
			}
		}
	}
	wg.Wait()
}
<|endoftext|>


<commit_before>/**
 * Copyright 2017 recipe-linebot
 */
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"unicode/utf8"

	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/line/line-bot-sdk-go/linebot/httphandler"
)

const RecipeOpenButtonLabel = "レシピを開く"
const PagingButtonLabel = "さらに読み込む"
const PagingCarouselTitle = "　"
const PagingCarouselText = "「%s」のレシピが他に %d 件あります。"
const PagingCarouselImageURL = "https://recipe-linebot.github.io/botserver/image/paging_%02d.jpg"
const NumPagingCarouselImages = 4
const NotFoundReplyStickerPackageID = "2"
const NotFoundReplyStickerID = "38"
const MaxRecipesToReply = 5
const MaxRecipeDescLength = 60
const RecipeDescTailIfTooLong = "..."
const RecipeCarouselAltTextTailing = "..."

type PagingPostbackData struct {
	From     int    `json:"from"`
	RawQuery string `json:"rawQuery"`
}

func roundRecipeDescription(desc string) string {
	if len(desc) <= MaxRecipeDescLength {
		return desc
	}
	descEnd := MaxRecipeDescLength - len(RecipeDescTailIfTooLong)
	for !utf8.RuneStart(desc[descEnd]) {
		descEnd--
	}
	return desc[0:descEnd] + RecipeDescTailIfTooLong
}

func newRecipesMessage(result *RecipeDBSearchResult, rawQuery string, from int) *linebot.TemplateMessage {
	// Build the carousel columns
	var cols []*linebot.CarouselColumn
	needsPaging := false
	for i, hit := range result.Hits.Hits {
		if i == MaxRecipesToReply-1 {
			needsPaging = true
			break
		}
		col := linebot.NewCarouselColumn(hit.Source.ImageUrl, hit.Source.Title,
			roundRecipeDescription(hit.Source.Desc),
			linebot.NewURITemplateAction(RecipeOpenButtonLabel, hit.Source.Url))
		cols = append(cols, col)
	}

	// If next page exists, append button to fetch next result
	if needsPaging {
		var pbData PagingPostbackData
		pbData.RawQuery = rawQuery
		pbData.From = from + MaxRecipesToReply - 1
		pbDataAsJson, err := json.Marshal(pbData)
		if err != nil {
			log.Fatal(err)
		}
		col := linebot.NewCarouselColumn(
			fmt.Sprintf(PagingCarouselImageURL, (from/(MaxRecipesToReply-1)%NumPagingCarouselImages)),
			PagingCarouselTitle,
			fmt.Sprintf(PagingCarouselText, rawQuery, result.Hits.Total-pbData.From),
			linebot.NewPostbackTemplateAction(PagingButtonLabel, string(pbDataAsJson), ""))
		cols = append(cols, col)
	}

	// Build as carousel message
	altText := result.Hits.Hits[0].Source.Title + RecipeCarouselAltTextTailing
	tmpl := linebot.NewCarouselTemplate(cols...)
	return linebot.NewTemplateMessage(altText, tmpl)
}

func suggestRecipes(bot *linebot.Client, event *linebot.Event, rawQuery string, from int, config *RecipeLinebotConfig) {
	log.Printf("search: query=%s\n", rawQuery)
	result := searchRecipes(rawQuery, from, MaxRecipesToReply, config)
	var replyMsg linebot.Message
	if result.Hits.Total == 0 {
		replyMsg = linebot.NewStickerMessage(NotFoundReplyStickerPackageID, NotFoundReplyStickerID)
	} else {
		replyMsg = newRecipesMessage(&result, rawQuery, from)
	}
	if _, err := bot.ReplyMessage(event.ReplyToken, replyMsg).Do(); err != nil {
		log.Fatal(err)
	}
}

func serveAsBot(config *RecipeLinebotConfig) {
	log.Printf("start bot server: addr=%s, path=%s", config.BotServer.ListenAddr, config.BotServer.APIPath)
	handler, err := httphandler.New(config.BotServer.ChSecret, config.BotServer.ChToken)
	if err != nil {
		log.Fatal(err)
	}
	handler.HandleEvents(func(events []*linebot.Event, req *http.Request) {
		bot, err := handler.NewClient()
		if err != nil {
			log.Fatal(err)
			return
		}
		for _, event := range events {
			// Log an event receivement
			dispName := "(unknown)"
			profile, err := bot.GetProfile(event.Source.UserID).Do()
			if err == nil {
				dispName = profile.DisplayName
			} else {
				log.Print(err)
			}
			log.Printf("eventType: %s, userDispName\n", event.Type, dispName)

			// Suggest recipes according to user request
			switch event.Type {
			case linebot.EventTypeMessage:
				switch recvMsg := event.Message.(type) {
				case *linebot.TextMessage:
					suggestRecipes(bot, event, recvMsg.Text, 0, config)
				}
			case linebot.EventTypePostback:
				var pbData PagingPostbackData
				json.Unmarshal([]byte(event.Postback.Data), &pbData)
				suggestRecipes(bot, event, pbData.RawQuery, pbData.From, config)
			}
		}
	})
	http.Handle(config.BotServer.APIPath, handler)
	if err := http.ListenAndServeTLS(config.BotServer.ListenAddr, config.BotServer.CertFilePath,
		config.BotServer.KeyFilePath, nil); err != nil {
		log.Fatal(err)
	}
	log.Print("bot server finished")
}
<commit_msg>Rename variable 'from' to 'offset'<commit_after>/**
 * Copyright 2017 recipe-linebot
 */
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"unicode/utf8"

	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/line/line-bot-sdk-go/linebot/httphandler"
)

const RecipeOpenButtonLabel = "レシピを開く"
const PagingButtonLabel = "さらに読み込む"
const PagingCarouselTitle = "　"
const PagingCarouselText = "「%s」のレシピが他に %d 件あります。"
const PagingCarouselImageURL = "https://recipe-linebot.github.io/botserver/image/paging_%02d.jpg"
const NumPagingCarouselImages = 4
const NotFoundReplyStickerPackageID = "2"
const NotFoundReplyStickerID = "38"
const MaxRecipesToReply = 5
const MaxRecipeDescLength = 60
const RecipeDescTailIfTooLong = "..."
const RecipeCarouselAltTextTailing = "..."

type PagingPostbackData struct {
	Offset   int    `json:"offset"`
	RawQuery string `json:"rawQuery"`
}

func roundRecipeDescription(desc string) string {
	if len(desc) <= MaxRecipeDescLength {
		return desc
	}
	descEnd := MaxRecipeDescLength - len(RecipeDescTailIfTooLong)
	for !utf8.RuneStart(desc[descEnd]) {
		descEnd--
	}
	return desc[0:descEnd] + RecipeDescTailIfTooLong
}

func newRecipesMessage(result *RecipeDBSearchResult, rawQuery string, offset int) *linebot.TemplateMessage {
	// Build the carousel columns
	var cols []*linebot.CarouselColumn
	needsPaging := false
	for i, hit := range result.Hits.Hits {
		if i == MaxRecipesToReply-1 {
			needsPaging = true
			break
		}
		col := linebot.NewCarouselColumn(hit.Source.ImageUrl, hit.Source.Title,
			roundRecipeDescription(hit.Source.Desc),
			linebot.NewURITemplateAction(RecipeOpenButtonLabel, hit.Source.Url))
		cols = append(cols, col)
	}

	// If next page exists, append button to fetch next result
	if needsPaging {
		var pbData PagingPostbackData
		pbData.RawQuery = rawQuery
		pbData.Offset = offset + MaxRecipesToReply - 1
		pbDataAsJson, err := json.Marshal(pbData)
		if err != nil {
			log.Fatal(err)
		}
		col := linebot.NewCarouselColumn(
			fmt.Sprintf(PagingCarouselImageURL, (offset/(MaxRecipesToReply-1)%NumPagingCarouselImages)),
			PagingCarouselTitle,
			fmt.Sprintf(PagingCarouselText, rawQuery, result.Hits.Total-pbData.Offset),
			linebot.NewPostbackTemplateAction(PagingButtonLabel, string(pbDataAsJson), ""))
		cols = append(cols, col)
	}

	// Build as carousel message
	altText := result.Hits.Hits[0].Source.Title + RecipeCarouselAltTextTailing
	tmpl := linebot.NewCarouselTemplate(cols...)
	return linebot.NewTemplateMessage(altText, tmpl)
}

func suggestRecipes(bot *linebot.Client, event *linebot.Event, rawQuery string, offset int, config *RecipeLinebotConfig) {
	log.Printf("search: query=%s\n", rawQuery)
	result := searchRecipes(rawQuery, offset, MaxRecipesToReply, config)
	var replyMsg linebot.Message
	if result.Hits.Total == 0 {
		replyMsg = linebot.NewStickerMessage(NotFoundReplyStickerPackageID, NotFoundReplyStickerID)
	} else {
		replyMsg = newRecipesMessage(&result, rawQuery, offset)
	}
	if _, err := bot.ReplyMessage(event.ReplyToken, replyMsg).Do(); err != nil {
		log.Fatal(err)
	}
}

func serveAsBot(config *RecipeLinebotConfig) {
	log.Printf("start bot server: addr=%s, path=%s", config.BotServer.ListenAddr, config.BotServer.APIPath)
	handler, err := httphandler.New(config.BotServer.ChSecret, config.BotServer.ChToken)
	if err != nil {
		log.Fatal(err)
	}
	handler.HandleEvents(func(events []*linebot.Event, req *http.Request) {
		bot, err := handler.NewClient()
		if err != nil {
			log.Fatal(err)
			return
		}
		for _, event := range events {
			// Log an event receivement
			dispName := "(unknown)"
			profile, err := bot.GetProfile(event.Source.UserID).Do()
			if err == nil {
				dispName = profile.DisplayName
			} else {
				log.Print(err)
			}
			log.Printf("eventType: %s, userDispName\n", event.Type, dispName)

			// Suggest recipes according to user request
			switch event.Type {
			case linebot.EventTypeMessage:
				switch recvMsg := event.Message.(type) {
				case *linebot.TextMessage:
					suggestRecipes(bot, event, recvMsg.Text, 0, config)
				}
			case linebot.EventTypePostback:
				var pbData PagingPostbackData
				json.Unmarshal([]byte(event.Postback.Data), &pbData)
				suggestRecipes(bot, event, pbData.RawQuery, pbData.Offset, config)
			}
		}
	})
	http.Handle(config.BotServer.APIPath, handler)
	if err := http.ListenAndServeTLS(config.BotServer.ListenAddr, config.BotServer.CertFilePath,
		config.BotServer.KeyFilePath, nil); err != nil {
		log.Fatal(err)
	}
	log.Print("bot server finished")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"time"
)

func tokenFromEnvOrWeb(ctx context.Context, config *oauth2.Config) (*oauth2.Token, error) {
	code := os.Getenv("OAUTH")
	if code == "" {
		var err error
		code, err = tokenFromWeb(ctx, config)
		if err != nil {
			config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob"
			authURL := config.AuthCodeURL("")
			return nil, fmt.Errorf("Authorize this app at %s and rerun with environment variable 'OAUTH' set to the auth code!", authURL)
		}
	}
	token, err := config.Exchange(ctx, code)
	return token, err
}

func tokenFromWeb(ctx context.Context, config *oauth2.Config) (string, error) {
	ch := make(chan string)
	randState := fmt.Sprintf("st%d", time.Now().UnixNano())
	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/favicon.ico" {
			http.Error(rw, "", 404)
			return
		}
		if req.FormValue("state") != randState {
			log.Printf("State doesn't match: req = %#v", req)
			http.Error(rw, "", 500)
			return
		}
		if code := req.FormValue("code"); code != "" {
			fmt.Fprintf(rw, "
Success
Authorized.")
			rw.(http.Flusher).Flush()
			ch <- code
			return
		}
		http.Error(rw, "", 500)
	}))
	defer ts.Close()
	config.RedirectURL = ts.URL
	authURL := config.AuthCodeURL(randState)
	errs := make(chan error)
	go func() {
		err := openURL(authURL)
		errs <- err
	}()
	err := <-errs
	if err == nil {
		code := <-ch
		return code, nil
	} else {
		return "", err
	}
}

func openURL(url string) error {
	try := []string{"xdg-open", "google-chrome", "open"}
	for _, bin := range try {
		err := exec.Command(bin, url).Run()
		if err == nil {
			return nil
		}
	}
	return fmt.Errorf("Error opening URL in browser.")
}
<commit_msg>Add option to skip browser altogether.<commit_after>package main

import (
	"fmt"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"time"
)

func tokenFromEnvOrWeb(ctx context.Context, config *oauth2.Config) (*oauth2.Token, error) {
	code := os.Getenv("OAUTH")
	if code == "NOBROWSER" {
		authURL := config.AuthCodeURL("")
		return nil, fmt.Errorf("Authorize this app at %s and rerun with environment variable 'OAUTH' set to the auth code!", authURL)
	} else if code == "" {
		var err error
		print("Launching browser for OAuth exchange. If running remotely or no browser is installed, rerun with environment varibale 'OAUTH' set to 'NOBROWSER'.\n")
		code, err = tokenFromWeb(ctx, config)
		if err != nil {
			config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob"
			authURL := config.AuthCodeURL("")
			return nil, fmt.Errorf("Authorize this app at %s and rerun with environment variable 'OAUTH' set to the auth code!", authURL)
		}
	}
	token, err := config.Exchange(ctx, code)
	return token, err
}

func tokenFromWeb(ctx context.Context, config *oauth2.Config) (string, error) {
	ch := make(chan string)
	randState := fmt.Sprintf("st%d", time.Now().UnixNano())
	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		if req.URL.Path == "/favicon.ico" {
			http.Error(rw, "", 404)
			return
		}
		if req.FormValue("state") != randState {
			log.Printf("State doesn't match: req = %#v", req)
			http.Error(rw, "", 500)
			return
		}
		if code := req.FormValue("code"); code != "" {
			fmt.Fprintf(rw, "
Success
Authorized.")
			rw.(http.Flusher).Flush()
			ch <- code
			return
		}
		http.Error(rw, "", 500)
	}))
	defer ts.Close()
	config.RedirectURL = ts.URL
	authURL := config.AuthCodeURL(randState)
	errs := make(chan error)
	go func() {
		err := openURL(authURL)
		errs <- err
	}()
	err := <-errs
	if err == nil {
		code := <-ch
		return code, nil
	} else {
		return "", err
	}
}

func openURL(url string) error {
	try := []string{"xdg-open", "google-chrome", "open"}
	for _, bin := range try {
		err := exec.Command(bin, url).Run()
		if err == nil {
			return nil
		}
	}
	return fmt.Errorf("Error opening URL in browser.")
}
<|endoftext|>