<commit_before>package gojison

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

func init() {
}

func TestResponse(t *testing.T) {
	w := httptest.NewRecorder()
	r := &http.Request{}
	Response(TestHandler{}).ServeHTTP(w, r)
	if w.Header().Get("Content-Type") != "application/json" {
		t.Error("Expected content-type to be set to application/json")
	}
}

type TestHandler struct{}

func (th TestHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
}
<commit_msg>Add test for the Request middleware.<commit_after>package gojison

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/zenazn/goji/web"
)

func init() {
}

func TestResponse(t *testing.T) {
	w := httptest.NewRecorder()
	r := &http.Request{}
	Response(TestHandler{}).ServeHTTP(w, r)
	if w.Header().Get("Content-Type") != "application/json" {
		t.Error("Expected content-type to be set to application/json")
	}
}

func TestRequest(t *testing.T) {
	request := []byte(`
		{
			"one": 1,
			"nested":{
				"two": 2
			}
		}
	`)
	body := bytes.NewBuffer(request)
	w := httptest.NewRecorder()
	c := &web.C{Env: make(map[string]interface{})}
	r, err := http.NewRequest("POST", "/", body)

	if err != nil {
		t.Error(err)
	}

	r.Header = map[string][]string{
		"Content-Type": {"application/json"},
	}

	Request(c, TestHandler{}).ServeHTTP(w, r)
	paramsAsInterface, ok := c.Env["Params"]
	if !ok {
		t.Error("Expected params to be set into the context.")
	}

	params, ok := paramsAsInterface.(Params)
	if params == nil || !ok {
		t.Error("Expected params to be unmarshalled into the context.")
	}

	if c.Env["GojisonDecodeError"] != nil {
		t.Error("Expected params to be decoded without an error.")
	}

	if params.Get("one") != "1" {
		wrong(t, "Get on unmarshaled params", "1", params.Get("one"))
	}

	if params.GetP("nested").GetInt("two") != 2 {
		wrong(t, "GetP#GetInt on unmarshaled params", 2, params.GetP("nested").GetInt("two"))
	}
}

func TestRequestWithoutJSON(t *testing.T) {
	request := []byte{}
	body := bytes.NewBuffer(request)
	w := httptest.NewRecorder()
	c := &web.C{Env: make(map[string]interface{})}
	r, err := http.NewRequest("GET", "/", body)

	if err != nil {
		t.Error(err)
	}

	r.Header = map[string][]string{}

	Request(c, TestHandler{}).ServeHTTP(w, r)
	_, ok := c.Env["Params"]
	if ok {
		t.Error("Expected params to be empty when there is no application/json request.")
	}
}

func TestRequestWithInvalidBody(t *testing.T) {
	request := []byte(`
		{"one"}
	`)
	body := bytes.NewBuffer(request)
	w := httptest.NewRecorder()
	c := &web.C{Env: make(map[string]interface{})}
	r, err := http.NewRequest("POST", "/", body)

	if err != nil {
		t.Error(err)
	}

	r.Header = map[string][]string{
		"Content-Type": {"application/json"},
	}

	Request(c, TestHandler{}).ServeHTTP(w, r)
	_, ok := c.Env["Params"]
	if !ok {
		t.Error("Expected params to be set into the context.")
	}

	if c.Env["GojisonDecodeError"] == nil {
		t.Error("Expected to be an Error when decoding the params.")
	}
}

type TestHandler struct{}

func (th TestHandler) ServeHTTP(_ http.ResponseWriter, _ *http.Request) {
}
<|endoftext|>


<commit_before>package solidserver

import (
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/hashicorp/terraform/terraform"
	"regexp"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"host": {
				Type:        schema.TypeString,
				Required:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_HOST", nil),
				Description: "SOLIDServer Hostname or IP address",
			},
			"username": {
				Type:        schema.TypeString,
				Required:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_USERNAME", nil),
				Description: "SOLIDServer API user's ID",
			},
			"password": {
				Type:        schema.TypeString,
				Required:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_PASSWORD", nil),
				Description: "SOLIDServer API user's password",
			},
			"sslverify": {
				Type:        schema.TypeBool,
				Required:    false,
				Optional:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_SSLVERIFY", true),
				Description: "Enable/Disable ssl verify (Default : enabled)",
			},
			"additional_trust_certs_file": {
				Type:        schema.TypeString,
				Required:    false,
				Optional:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_ADDITIONALTRUSTCERTSFILE", nil),
				Description: "PEM formatted file with additional certificates to trust for TLS connection",
			},
			"version": {
				Type:         schema.TypeString,
				Required:     false,
				Optional:     true,
				DefaultFunc:  schema.EnvDefaultFunc("SOLIDServer_VERSION", ""),
				ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[0-9]\.[0-9]\.[0-9]\.([pP][0-9]+[a-z]?)?$`), "Invalid Version number pattern"),
				Description:  "SOLIDServer Version in case API user does not have admin permissions",
			},
		},

		DataSourcesMap: map[string]*schema.Resource{
			"solidserver_ip_space":   dataSourceipspace(),
			"solidserver_ip_subnet":  dataSourceipsubnet(),
			"solidserver_ip_pool":    dataSourceippool(),
			"solidserver_ip_address": dataSourceipaddress(),
			"solidserver_ip_ptr":     dataSourceipptr(),
			"solidserver_ip6_ptr":    dataSourceip6ptr(),
			"solidserver_dns_smart":  dataSourcednssmart(),
			"solidserver_dns_server": dataSourcednsserver(),
			"solidserver_dns_zone":   dataSourcednszone(),
			"solidserver_usergroup":  dataSourceusergroup(),
			"solidserver_cdb":        dataSourcecdb(),
			"solidserver_cdb_data":   dataSourcecdbdata(),
		},

		ResourcesMap: map[string]*schema.Resource{
			"solidserver_ip_space":         resourceipspace(),
			"solidserver_ip_subnet":        resourceipsubnet(),
			"solidserver_ip6_subnet":       resourceip6subnet(),
			"solidserver_ip_pool":          resourceippool(),
			"solidserver_ip6_pool":         resourceip6pool(),
			"solidserver_ip_address":       resourceipaddress(),
			"solidserver_ip6_address":      resourceip6address(),
			"solidserver_ip_alias":         resourceipalias(),
			"solidserver_ip6_alias":        resourceip6alias(),
			"solidserver_ip_mac":           resourceipmac(),
			"solidserver_ip6_mac":          resourceip6mac(),
			"solidserver_device":           resourcedevice(),
			"solidserver_vlan_domain":      resourcevlandomain(),
			"solidserver_vlan":             resourcevlan(),
			"solidserver_dns_smart":        resourcednssmart(),
			"solidserver_dns_server":       resourcednsserver(),
			"solidserver_dns_view":         resourcednsview(),
			"solidserver_dns_zone":         resourcednszone(),
			"solidserver_dns_forward_zone": resourcednsforwardzone(),
			"solidserver_dns_rr":           resourcednsrr(),
			"solidserver_app_application":  resourceapplication(),
			"solidserver_app_pool":         resourceapplicationpool(),
			"solidserver_app_node":         resourceapplicationnode(),
			"solidserver_user":             resourceuser(),
			"solidserver_usergroup":        resourceusergroup(),
			"solidserver_cdb":              resourcecdb(),
			"solidserver_cdb_data":         resourcecdbdata(),
		},

		ConfigureFunc: ProviderConfigure,
	}
}

func ProviderConfigure(d *schema.ResourceData) (interface{}, error) {
	s, err := NewSOLIDserver(
		d.Get("host").(string),
		d.Get("username").(string),
		d.Get("password").(string),
		d.Get("sslverify").(bool),
		d.Get("additional_trust_certs_file").(string),
		d.Get("version").(string),
	)

	return s, err
}
<commit_msg>FIxing conflict with solidserver version field<commit_after>package solidserver

import (
	"github.com/hashicorp/terraform/helper/schema"
	"github.com/hashicorp/terraform/helper/validation"
	"github.com/hashicorp/terraform/terraform"
	"regexp"
)

func Provider() terraform.ResourceProvider {
	return &schema.Provider{
		Schema: map[string]*schema.Schema{
			"host": {
				Type:        schema.TypeString,
				Required:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_HOST", nil),
				Description: "SOLIDServer Hostname or IP address",
			},
			"username": {
				Type:        schema.TypeString,
				Required:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_USERNAME", nil),
				Description: "SOLIDServer API user's ID",
			},
			"password": {
				Type:        schema.TypeString,
				Required:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_PASSWORD", nil),
				Description: "SOLIDServer API user's password",
			},
			"sslverify": {
				Type:        schema.TypeBool,
				Required:    false,
				Optional:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_SSLVERIFY", true),
				Description: "Enable/Disable ssl verify (Default : enabled)",
			},
			"additional_trust_certs_file": {
				Type:        schema.TypeString,
				Required:    false,
				Optional:    true,
				DefaultFunc: schema.EnvDefaultFunc("SOLIDServer_ADDITIONALTRUSTCERTSFILE", nil),
				Description: "PEM formatted file with additional certificates to trust for TLS connection",
			},
			"solidserverversion": {
				Type:         schema.TypeString,
				Required:     false,
				Optional:     true,
				DefaultFunc:  schema.EnvDefaultFunc("SOLIDServer_VERSION", ""),
				ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([0-9]\.[0-9]\.[0-9]\.([pP][0-9]+[a-z]?)?)?$`), "Invalid Version Number"),
				Description:  "SOLIDServer Version in case API user does not have admin permissions",
			},
		},

		DataSourcesMap: map[string]*schema.Resource{
			"solidserver_ip_space":   dataSourceipspace(),
			"solidserver_ip_subnet":  dataSourceipsubnet(),
			"solidserver_ip_pool":    dataSourceippool(),
			"solidserver_ip_address": dataSourceipaddress(),
			"solidserver_ip_ptr":     dataSourceipptr(),
			"solidserver_ip6_ptr":    dataSourceip6ptr(),
			"solidserver_dns_smart":  dataSourcednssmart(),
			"solidserver_dns_server": dataSourcednsserver(),
			"solidserver_dns_zone":   dataSourcednszone(),
			"solidserver_usergroup":  dataSourceusergroup(),
			"solidserver_cdb":        dataSourcecdb(),
			"solidserver_cdb_data":   dataSourcecdbdata(),
		},

		ResourcesMap: map[string]*schema.Resource{
			"solidserver_ip_space":         resourceipspace(),
			"solidserver_ip_subnet":        resourceipsubnet(),
			"solidserver_ip6_subnet":       resourceip6subnet(),
			"solidserver_ip_pool":          resourceippool(),
			"solidserver_ip6_pool":         resourceip6pool(),
			"solidserver_ip_address":       resourceipaddress(),
			"solidserver_ip6_address":      resourceip6address(),
			"solidserver_ip_alias":         resourceipalias(),
			"solidserver_ip6_alias":        resourceip6alias(),
			"solidserver_ip_mac":           resourceipmac(),
			"solidserver_ip6_mac":          resourceip6mac(),
			"solidserver_device":           resourcedevice(),
			"solidserver_vlan_domain":      resourcevlandomain(),
			"solidserver_vlan":             resourcevlan(),
			"solidserver_dns_smart":        resourcednssmart(),
			"solidserver_dns_server":       resourcednsserver(),
			"solidserver_dns_view":         resourcednsview(),
			"solidserver_dns_zone":         resourcednszone(),
			"solidserver_dns_forward_zone": resourcednsforwardzone(),
			"solidserver_dns_rr":           resourcednsrr(),
			"solidserver_app_application":  resourceapplication(),
			"solidserver_app_pool":         resourceapplicationpool(),
			"solidserver_app_node":         resourceapplicationnode(),
			"solidserver_user":             resourceuser(),
			"solidserver_usergroup":        resourceusergroup(),
			"solidserver_cdb":              resourcecdb(),
			"solidserver_cdb_data":         resourcecdbdata(),
		},

		ConfigureFunc: ProviderConfigure,
	}
}

func ProviderConfigure(d *schema.ResourceData) (interface{}, error) {
	s, err := NewSOLIDserver(
		d.Get("host").(string),
		d.Get("username").(string),
		d.Get("password").(string),
		d.Get("sslverify").(bool),
		d.Get("additional_trust_certs_file").(string),
		d.Get("solidserverversion").(string),
	)

	return s, err
}
<|endoftext|>


<commit_before>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package network

import (
	"bufio"
	"os"
	"strings"

	"github.com/juju/errors"
)

// DNSConfig holds a list of DNS nameserver addresses and default search
// domains.
type DNSConfig struct {
	Nameservers   []Address
	SearchDomains []string
}

// ParseResolvConf parses the given resolvConfPath (usually "/etc/resolv.conf")
// file (if present). It returns the values of any 'nameserver' stanzas, and the
// last 'search' stanza found, as defined by resolv.conf(5). Values in the
// result will appear in the order found, including duplicates. Parsing errors
// will be returned in these cases:
//
// 1. if a 'nameserver' or 'search' without a value is found;
// 2. 'nameserver' with more than one value (trailing comments starting with '#'
//    or ';' after the value are allowed).
// 3. if any value containing '#' or ';' (e.g. 'nameserver 8.8.8.8#bad'), because
//    values and comments following them must be separated by whitespace.
func ParseResolvConf(path string) (*DNSConfig, error) {
	file, err := os.Open(path)
	if os.IsNotExist(err) {
		logger.Debugf("%q does not exist - not parsing", path)
		return nil, nil
	} else if err != nil {
		return nil, errors.Trace(err)
	}
	defer file.Close()

	var (
		nameservers   []string
		searchDomains []string
	)
	scanner := bufio.NewScanner(file)
	lineNum := 0
	for scanner.Scan() {
		line := scanner.Text()
		lineNum++

		values, err := parseResolvStanza(line, "nameserver")
		if err != nil {
			return nil, errors.Annotatef(err, "parsing %q, line %d", path, lineNum)
		}

		if numValues := len(values); numValues > 1 {
			return nil, errors.Errorf(
				"parsing %q, line %d: one value expected for \"nameserver\", got %d",
				path, lineNum, numValues,
			)
		} else if numValues == 1 {
			nameservers = append(nameservers, values[0])
			continue
		}

		values, err = parseResolvStanza(line, "search")
		if err != nil {
			return nil, errors.Annotatef(err, "parsing %q, line %d", path, lineNum)
		}

		if len(values) > 0 {
			// Last 'search' found wins.
			searchDomains = append([]string(nil), values...)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, errors.Annotatef(err, "reading %q", path)
	}

	return &DNSConfig{
		Nameservers:   NewAddresses(nameservers...),
		SearchDomains: searchDomains,
	}, nil
}

func parseResolvStanza(line, stanza string) ([]string, error) {
	const commentChars = ";#"
	isComment := func(s string) bool {
		return strings.IndexAny(s, commentChars) == 0
	}

	line = strings.TrimSpace(line)
	fields := strings.Fields(line)
	noFields := len(fields) == 0 // line contains only whitespace

	if isComment(line) || noFields || fields[0] != stanza {
		// Lines starting with ';' or '#' are comments and are ignored. Empty
		// lines and those not starting with stanza are ignored.
		return nil, nil
	}

	// Mostly for convenience, comments starting with ';' or '#' after a value
	// are allowed and ignored, assuming there's whitespace between the value
	// and the comment (e.g. 'search foo #bar' is OK, but 'search foo#bar'
	// isn't).
	var parsedValues []string
	rawValues := fields[1:] // skip the stanza itself
	for _, value := range rawValues {
		if isComment(value) {
			// We're done parsing as the rest of the line is still part of the
			// same comment.
			break
		}

		if strings.ContainsAny(value, commentChars) {
			// This will catch cases like 'nameserver 8.8.8.8#foo', because
			// fields[1] will be '8.8.8.8#foo'.
			return nil, errors.Errorf("%q: invalid value %q", stanza, value)
		}

		parsedValues = append(parsedValues, value)
	}

	// resolv.conf(5) states that to be recognized as valid, the line must begin
	// with the stanza, followed by whitespace, then at least one value (for
	// 'nameserver', more values separated by whitespace are allowed for
	// 'search').
	if len(parsedValues) == 0 {
		return nil, errors.Errorf("%q: required value(s) missing", stanza)
	}

	return parsedValues, nil
}
<commit_msg>network: Doc comments re ParseResolvConf() improved<commit_after>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package network

import (
	"bufio"
	"os"
	"strings"

	"github.com/juju/errors"
)

// DNSConfig holds a list of DNS nameserver addresses and default search
// domains.
type DNSConfig struct {
	Nameservers   []Address
	SearchDomains []string
}

// ParseResolvConf parses a resolv.conf(5) file at the given path (usually
// "/etc/resolv.conf"), if present. Returns the values of any 'nameserver'
// stanzas, and the last 'search' stanza found. Values in the result will appear
// in the order found, including duplicates. Parsing errors will be returned in
// these cases:
//
// 1. if a 'nameserver' or 'search' without a value is found;
// 2. 'nameserver' with more than one value (trailing comments starting with '#'
//    or ';' after the value are allowed).
// 3. if any value containing '#' or ';' (e.g. 'nameserver 8.8.8.8#bad'), because
//    values and comments following them must be separated by whitespace.
//
// No error is returned if the file is missing. See resolv.conf(5) man page for
// details.
func ParseResolvConf(path string) (*DNSConfig, error) {
	file, err := os.Open(path)
	if os.IsNotExist(err) {
		logger.Debugf("%q does not exist - not parsing", path)
		return nil, nil
	} else if err != nil {
		return nil, errors.Trace(err)
	}
	defer file.Close()

	var (
		nameservers   []string
		searchDomains []string
	)
	scanner := bufio.NewScanner(file)
	lineNum := 0
	for scanner.Scan() {
		line := scanner.Text()
		lineNum++

		values, err := parseResolvStanza(line, "nameserver")
		if err != nil {
			return nil, errors.Annotatef(err, "parsing %q, line %d", path, lineNum)
		}

		if numValues := len(values); numValues > 1 {
			return nil, errors.Errorf(
				"parsing %q, line %d: one value expected for \"nameserver\", got %d",
				path, lineNum, numValues,
			)
		} else if numValues == 1 {
			nameservers = append(nameservers, values[0])
			continue
		}

		values, err = parseResolvStanza(line, "search")
		if err != nil {
			return nil, errors.Annotatef(err, "parsing %q, line %d", path, lineNum)
		}

		if len(values) > 0 {
			// Last 'search' found wins.
			searchDomains = values
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, errors.Annotatef(err, "reading %q", path)
	}

	return &DNSConfig{
		Nameservers:   NewAddresses(nameservers...),
		SearchDomains: searchDomains,
	}, nil
}

// parseResolvStanza parses a single line from a resolv.conf(5) file, beginning
// with the given stanza ('nameserver' or 'search' ). If the line does not
// contain the stanza, no results and no error is returned. Leading and trailing
// whitespace is removed first, then lines starting with ";" or "#" are treated
// as comments.
//
// Examples:
// parseResolvStanza(`   # nothing ;to see here`, "doesn't matter")
// will return (nil, nil) - comments and whitespace are ignored, nothing left.
//
// parseResolvStanza(`   nameserver    ns1.example.com   # preferred`, "nameserver")
// will return ([]string{"ns1.example.com"}, nil).
//
// parseResolvStanza(`search ;; bad: no value`, "search")
// will return (nil, err: `"search": required value(s) missing`)
//
// parseResolvStanza(`search foo bar foo foo.bar bar.foo ;; try all`, "search")
// will return ([]string("foo", "bar", "foo", "foo.bar", "bar.foo"}, nil)
//
// parseResolvStanza(`search foo#bad comment`, "nameserver")
// will return (nil, nil) - line does not start with "nameserver".
//
// parseResolvStanza(`search foo#bad comment`, "search")
// will return (nil, err: `"search": invalid value "foo#bad"`) - no whitespace
// between the value "foo" and the following comment "#bad comment".
func parseResolvStanza(line, stanza string) ([]string, error) {
	const commentChars = ";#"
	isComment := func(s string) bool {
		return strings.IndexAny(s, commentChars) == 0
	}

	line = strings.TrimSpace(line)
	fields := strings.Fields(line)
	noFields := len(fields) == 0 // line contains only whitespace

	if isComment(line) || noFields || fields[0] != stanza {
		// Lines starting with ';' or '#' are comments and are ignored. Empty
		// lines and those not starting with stanza are ignored.
		return nil, nil
	}

	// Mostly for convenience, comments starting with ';' or '#' after a value
	// are allowed and ignored, assuming there's whitespace between the value
	// and the comment (e.g. 'search foo #bar' is OK, but 'search foo#bar'
	// isn't).
	var parsedValues []string
	rawValues := fields[1:] // skip the stanza itself
	for _, value := range rawValues {
		if isComment(value) {
			// We're done parsing as the rest of the line is still part of the
			// same comment.
			break
		}

		if strings.ContainsAny(value, commentChars) {
			// This will catch cases like 'nameserver 8.8.8.8#foo', because
			// fields[1] will be '8.8.8.8#foo'.
			return nil, errors.Errorf("%q: invalid value %q", stanza, value)
		}

		parsedValues = append(parsedValues, value)
	}

	// resolv.conf(5) states that to be recognized as valid, the line must begin
	// with the stanza, followed by whitespace, then at least one value (for
	// 'nameserver', more values separated by whitespace are allowed for
	// 'search').
	if len(parsedValues) == 0 {
		return nil, errors.Errorf("%q: required value(s) missing", stanza)
	}

	return parsedValues, nil
}
<|endoftext|>


<commit_before>package bosh

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"

	"github.com/cloudfoundry/bosh-bootloader/helpers"
)

const gcpBoshDirectorEphemeralIPOps = `
- type: replace
  path: /networks/name=default/subnets/0/cloud_properties/ephemeral_external_ip?
  value: true
`

const awsBoshDirectorEphemeralIPOps = `
- type: replace
  path: /resource_pools/name=vms/cloud_properties/auto_assign_public_ip?
  value: true
`

const azureSSHStaticIP = `
- type: replace
  path: /cloud_provider/ssh_tunnel?
  value:
	host: ((external_ip))
`

type Executor struct {
	command       command
	tempDir       func(string, string) (string, error)
	readFile      func(string) ([]byte, error)
	unmarshalJSON func([]byte, interface{}) error
	marshalJSON   func(interface{}) ([]byte, error)
	writeFile     func(string, []byte, os.FileMode) error
}

type InterpolateInput struct {
	IAAS                  string
	DeploymentVars        string
	JumpboxDeploymentVars string
	BOSHState             map[string]interface{}
	Variables             string
	OpsFile               string
}

type InterpolateOutput struct {
	Variables string
	Manifest  string
}

type JumpboxInterpolateOutput struct {
	Variables string
	Manifest  string
}

type CreateEnvInput struct {
	Manifest  string
	Variables string
	State     map[string]interface{}
}

type CreateEnvOutput struct {
	State map[string]interface{}
}

type DeleteEnvInput struct {
	Manifest  string
	Variables string
	State     map[string]interface{}
}

type command interface {
	Run(stdout io.Writer, workingDirectory string, args []string) error
}

const VERSION_DEV_BUILD = "[DEV BUILD]"

func NewExecutor(cmd command, tempDir func(string, string) (string, error), readFile func(string) ([]byte, error),
	unmarshalJSON func([]byte, interface{}) error,
	marshalJSON func(interface{}) ([]byte, error), writeFile func(string, []byte, os.FileMode) error) Executor {
	return Executor{
		command:       cmd,
		tempDir:       tempDir,
		readFile:      readFile,
		unmarshalJSON: unmarshalJSON,
		marshalJSON:   marshalJSON,
		writeFile:     writeFile,
	}
}

func (e Executor) JumpboxInterpolate(interpolateInput InterpolateInput) (JumpboxInterpolateOutput, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		return JumpboxInterpolateOutput{}, fmt.Errorf("create temp dir: %s", err)
	}

	var jumpboxSetupFiles = map[string][]byte{
		"jumpbox-deployment-vars.yml": []byte(interpolateInput.JumpboxDeploymentVars),
		"jumpbox.yml":                 MustAsset("vendor/github.com/cppforlife/jumpbox-deployment/jumpbox.yml"),
		"cpi.yml":                     MustAsset(fmt.Sprintf("vendor/github.com/cppforlife/jumpbox-deployment/%s/cpi.yml", interpolateInput.IAAS)),
	}

	if interpolateInput.Variables != "" {
		jumpboxSetupFiles["variables.yml"] = []byte(interpolateInput.Variables)
	}

	for path, contents := range jumpboxSetupFiles {
		err = e.writeFile(filepath.Join(tempDir, path), contents, os.ModePerm)
		if err != nil {
			//not tested
			return JumpboxInterpolateOutput{}, fmt.Errorf("write file: %s", err)
		}
	}

	args := []string{
		"interpolate", filepath.Join(tempDir, "jumpbox.yml"),
		"--var-errs",
		"--vars-store", filepath.Join(tempDir, "variables.yml"),
		"--vars-file", filepath.Join(tempDir, "jumpbox-deployment-vars.yml"),
		"-o", filepath.Join(tempDir, "cpi.yml"),
	}

	buffer := bytes.NewBuffer([]byte{})
	err = e.command.Run(buffer, tempDir, args)
	if err != nil {
		return JumpboxInterpolateOutput{}, fmt.Errorf("bosh interpolate: %s: %s", err, buffer)
	}

	varsStore, err := e.readFile(filepath.Join(tempDir, "variables.yml"))
	if err != nil {
		return JumpboxInterpolateOutput{}, fmt.Errorf("read file: %s", err)
	}

	return JumpboxInterpolateOutput{
		Variables: string(varsStore),
		Manifest:  buffer.String(),
	}, nil
}

const awsEncryptDiskOps = `---
- type: replace
  path: /disk_pools/name=disks/cloud_properties?
  value:
    type: gp2
    encrypted: true
    kms_key_arn: ((kms_key_arn))
`

func (e Executor) DirectorInterpolate(interpolateInput InterpolateInput) (InterpolateOutput, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		//not tested
		return InterpolateOutput{}, err
	}

	var directorSetupFiles = map[string][]byte{
		"deployment-vars.yml":                    []byte(interpolateInput.DeploymentVars),
		"user-ops-file.yml":                      []byte(interpolateInput.OpsFile),
		"bosh.yml":                               MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/bosh.yml"),
		"cpi.yml":                                MustAsset(fmt.Sprintf("vendor/github.com/cloudfoundry/bosh-deployment/%s/cpi.yml", interpolateInput.IAAS)),
		"iam-instance-profile.yml":               MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/aws/iam-instance-profile.yml"),
		"use-managed-disks.yml":                  MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/azure/use-managed-disks.yml"),
		"gcp-bosh-director-ephemeral-ip-ops.yml": []byte(gcpBoshDirectorEphemeralIPOps),
		"aws-bosh-director-ephemeral-ip-ops.yml": []byte(awsBoshDirectorEphemeralIPOps),
		"aws-bosh-director-encrypt-disk-ops.yml": []byte(awsEncryptDiskOps),
		"azure-ssh-static-ip.yml":                []byte(azureSSHStaticIP),
		"jumpbox-user.yml":                       MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/jumpbox-user.yml"),
		"gcp-external-ip-not-recommended.yml":    MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/external-ip-not-recommended.yml"),
		"azure-external-ip-not-recommended.yml":  MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/external-ip-not-recommended.yml"),
		"aws-external-ip-not-recommended.yml":    MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/external-ip-with-registry-not-recommended.yml"),
		"uaa.yml":     MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/uaa.yml"),
		"credhub.yml": MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/credhub.yml"),
	}

	if interpolateInput.Variables != "" {
		directorSetupFiles["variables.yml"] = []byte(interpolateInput.Variables)
	}

	for path, contents := range directorSetupFiles {
		err = e.writeFile(filepath.Join(tempDir, path), contents, os.ModePerm)
		if err != nil {
			//not tested
			return InterpolateOutput{}, err
		}
	}

	var args = []string{
		"interpolate", filepath.Join(tempDir, "bosh.yml"),
		"--var-errs",
		"--vars-store", filepath.Join(tempDir, "variables.yml"),
		"--vars-file", filepath.Join(tempDir, "deployment-vars.yml"),
		"-o", filepath.Join(tempDir, "cpi.yml"),
	}

	// if interpolateInput.IAAS == "azure" {
	// 	args = append(args, "-o", filepath.Join(tempDir, "use-managed-disks.yml"))
	// }

	if interpolateInput.JumpboxDeploymentVars == "" {
		args = append(args,
			"-o", filepath.Join(tempDir, "jumpbox-user.yml"),
		)

		switch interpolateInput.IAAS {
		case "gcp":
			args = append(args, "-o", filepath.Join(tempDir, "gcp-external-ip-not-recommended.yml"))
		case "aws":
			args = append(args, "-o", filepath.Join(tempDir, "aws-external-ip-not-recommended.yml"))
		case "azure":
			args = append(args, "-o", filepath.Join(tempDir, "azure-external-ip-not-recommended.yml"))
		}
	} else {
		args = append(args,
			"-o", filepath.Join(tempDir, "uaa.yml"),
			"-o", filepath.Join(tempDir, "credhub.yml"),
		)
		switch interpolateInput.IAAS {
		case "gcp":
			args = append(args, "-o", filepath.Join(tempDir, "gcp-bosh-director-ephemeral-ip-ops.yml"))
		case "aws":
			args = append(args, "-o", filepath.Join(tempDir, "aws-bosh-director-ephemeral-ip-ops.yml"))
		case "azure":
			args = append(args, "-o", filepath.Join(tempDir, "azure-ssh-static-ip.yml"))
		}
	}

	if interpolateInput.IAAS == "aws" {
		args = append(args,
			"-o", filepath.Join(tempDir, "iam-instance-profile.yml"),
			"-o", filepath.Join(tempDir, "aws-bosh-director-encrypt-disk-ops.yml"),
		)
	}

	buffer := bytes.NewBuffer([]byte{})
	err = e.command.Run(buffer, tempDir, args)
	if err != nil {
		return InterpolateOutput{}, err
	}

	if interpolateInput.OpsFile != "" {
		err = e.writeFile(filepath.Join(tempDir, "bosh.yml"), buffer.Bytes(), os.ModePerm)
		if err != nil {
			//not tested
			return InterpolateOutput{}, err
		}

		args = []string{
			"interpolate", filepath.Join(tempDir, "bosh.yml"),
			"--var-errs",
			"--vars-store", filepath.Join(tempDir, "variables.yml"),
			"--vars-file", filepath.Join(tempDir, "deployment-vars.yml"),
			"-o", filepath.Join(tempDir, "user-ops-file.yml"),
		}

		buffer = bytes.NewBuffer([]byte{})
		err = e.command.Run(buffer, tempDir, args)
		if err != nil {
			return InterpolateOutput{}, err
		}
	}

	varsStore, err := e.readFile(filepath.Join(tempDir, "variables.yml"))
	if err != nil {
		return InterpolateOutput{}, err
	}

	return InterpolateOutput{
		Variables: string(varsStore),
		Manifest:  buffer.String(),
	}, nil
}

func (e Executor) CreateEnv(createEnvInput CreateEnvInput) (CreateEnvOutput, error) {
	tempDir, err := e.writePreviousFiles(createEnvInput.State, createEnvInput.Variables, createEnvInput.Manifest)
	if err != nil {
		return CreateEnvOutput{}, err
	}

	statePath := fmt.Sprintf("%s/state.json", tempDir)
	variablesPath := fmt.Sprintf("%s/variables.yml", tempDir)
	manifestPath := filepath.Join(tempDir, "manifest.yml")

	args := []string{
		"create-env", manifestPath,
		"--vars-store", variablesPath,
		"--state", statePath,
	}

	err = e.command.Run(os.Stdout, tempDir, args)
	if err != nil {
		state, readErr := e.readBOSHState(statePath)
		if readErr != nil {
			errorList := helpers.Errors{}
			errorList.Add(err)
			errorList.Add(readErr)
			return CreateEnvOutput{}, errorList
		}

		return CreateEnvOutput{}, NewCreateEnvError(state, err)
	}

	state, err := e.readBOSHState(statePath)
	if err != nil {
		return CreateEnvOutput{}, err
	}

	return CreateEnvOutput{
		State: state,
	}, nil
}

func (e Executor) readBOSHState(statePath string) (map[string]interface{}, error) {
	stateContents, err := e.readFile(statePath)
	if err != nil {
		return map[string]interface{}{}, err
	}

	var state map[string]interface{}
	err = e.unmarshalJSON(stateContents, &state)
	if err != nil {
		return map[string]interface{}{}, err
	}

	return state, nil
}

func (e Executor) DeleteEnv(deleteEnvInput DeleteEnvInput) error {
	tempDir, err := e.writePreviousFiles(deleteEnvInput.State, deleteEnvInput.Variables, deleteEnvInput.Manifest)
	if err != nil {
		return err
	}

	statePath := fmt.Sprintf("%s/state.json", tempDir)
	variablesPath := fmt.Sprintf("%s/variables.yml", tempDir)
	boshManifestPath := filepath.Join(tempDir, "manifest.yml")

	args := []string{
		"delete-env", boshManifestPath,
		"--vars-store", variablesPath,
		"--state", statePath,
	}

	err = e.command.Run(os.Stdout, tempDir, args)
	if err != nil {
		state, readErr := e.readBOSHState(statePath)
		if readErr != nil {
			errorList := helpers.Errors{}
			errorList.Add(err)
			errorList.Add(readErr)
			return errorList
		}
		return NewDeleteEnvError(state, err)
	}

	return nil
}

func (e Executor) Version() (string, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		return "", err
	}

	args := []string{"-v"}

	buffer := bytes.NewBuffer([]byte{})
	err = e.command.Run(buffer, tempDir, args)
	if err != nil {
		return "", err
	}

	versionOutput := buffer.String()
	regex := regexp.MustCompile(`\d+.\d+.\d+`)

	version := regex.FindString(versionOutput)
	if version == "" {
		return "", NewBOSHVersionError(errors.New("BOSH version could not be parsed"))
	}

	return version, nil
}

func (e Executor) writePreviousFiles(state map[string]interface{}, variables, manifest string) (string, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		return "", err
	}

	statePath := fmt.Sprintf("%s/state.json", tempDir)
	variablesPath := fmt.Sprintf("%s/variables.yml", tempDir)
	boshManifestPath := filepath.Join(tempDir, "manifest.yml")

	if state != nil {
		boshStateContents, err := e.marshalJSON(state)
		if err != nil {
			return "", err
		}
		err = e.writeFile(statePath, boshStateContents, os.ModePerm)
		if err != nil {
			return "", err
		}
	}

	err = e.writeFile(variablesPath, []byte(variables), os.ModePerm)
	if err != nil {
		// not tested
		return "", err
	}

	err = e.writeFile(boshManifestPath, []byte(manifest), os.ModePerm)
	if err != nil {
		// not tested
		return "", err
	}

	return tempDir, nil
}
<commit_msg>Fix no ip Ops for Azure<commit_after>package bosh

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"

	"github.com/cloudfoundry/bosh-bootloader/helpers"
)

const gcpBoshDirectorEphemeralIPOps = `
- type: replace
  path: /networks/name=default/subnets/0/cloud_properties/ephemeral_external_ip?
  value: true
`

const awsBoshDirectorEphemeralIPOps = `
- type: replace
  path: /resource_pools/name=vms/cloud_properties/auto_assign_public_ip?
  value: true
`

const azureSSHStaticIP = `
- type: replace
  path: /cloud_provider/ssh_tunnel/host
  value: ((external_ip))
`

type Executor struct {
	command       command
	tempDir       func(string, string) (string, error)
	readFile      func(string) ([]byte, error)
	unmarshalJSON func([]byte, interface{}) error
	marshalJSON   func(interface{}) ([]byte, error)
	writeFile     func(string, []byte, os.FileMode) error
}

type InterpolateInput struct {
	IAAS                  string
	DeploymentVars        string
	JumpboxDeploymentVars string
	BOSHState             map[string]interface{}
	Variables             string
	OpsFile               string
}

type InterpolateOutput struct {
	Variables string
	Manifest  string
}

type JumpboxInterpolateOutput struct {
	Variables string
	Manifest  string
}

type CreateEnvInput struct {
	Manifest  string
	Variables string
	State     map[string]interface{}
}

type CreateEnvOutput struct {
	State map[string]interface{}
}

type DeleteEnvInput struct {
	Manifest  string
	Variables string
	State     map[string]interface{}
}

type command interface {
	Run(stdout io.Writer, workingDirectory string, args []string) error
}

const VERSION_DEV_BUILD = "[DEV BUILD]"

func NewExecutor(cmd command, tempDir func(string, string) (string, error), readFile func(string) ([]byte, error),
	unmarshalJSON func([]byte, interface{}) error,
	marshalJSON func(interface{}) ([]byte, error), writeFile func(string, []byte, os.FileMode) error) Executor {
	return Executor{
		command:       cmd,
		tempDir:       tempDir,
		readFile:      readFile,
		unmarshalJSON: unmarshalJSON,
		marshalJSON:   marshalJSON,
		writeFile:     writeFile,
	}
}

func (e Executor) JumpboxInterpolate(interpolateInput InterpolateInput) (JumpboxInterpolateOutput, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		return JumpboxInterpolateOutput{}, fmt.Errorf("create temp dir: %s", err)
	}

	var jumpboxSetupFiles = map[string][]byte{
		"jumpbox-deployment-vars.yml": []byte(interpolateInput.JumpboxDeploymentVars),
		"jumpbox.yml":                 MustAsset("vendor/github.com/cppforlife/jumpbox-deployment/jumpbox.yml"),
		"cpi.yml":                     MustAsset(fmt.Sprintf("vendor/github.com/cppforlife/jumpbox-deployment/%s/cpi.yml", interpolateInput.IAAS)),
	}

	if interpolateInput.Variables != "" {
		jumpboxSetupFiles["variables.yml"] = []byte(interpolateInput.Variables)
	}

	for path, contents := range jumpboxSetupFiles {
		err = e.writeFile(filepath.Join(tempDir, path), contents, os.ModePerm)
		if err != nil {
			//not tested
			return JumpboxInterpolateOutput{}, fmt.Errorf("write file: %s", err)
		}
	}

	args := []string{
		"interpolate", filepath.Join(tempDir, "jumpbox.yml"),
		"--var-errs",
		"--vars-store", filepath.Join(tempDir, "variables.yml"),
		"--vars-file", filepath.Join(tempDir, "jumpbox-deployment-vars.yml"),
		"-o", filepath.Join(tempDir, "cpi.yml"),
	}

	buffer := bytes.NewBuffer([]byte{})
	err = e.command.Run(buffer, tempDir, args)
	if err != nil {
		return JumpboxInterpolateOutput{}, fmt.Errorf("bosh interpolate: %s: %s", err, buffer)
	}

	varsStore, err := e.readFile(filepath.Join(tempDir, "variables.yml"))
	if err != nil {
		return JumpboxInterpolateOutput{}, fmt.Errorf("read file: %s", err)
	}

	return JumpboxInterpolateOutput{
		Variables: string(varsStore),
		Manifest:  buffer.String(),
	}, nil
}

const awsEncryptDiskOps = `---
- type: replace
  path: /disk_pools/name=disks/cloud_properties?
  value:
    type: gp2
    encrypted: true
    kms_key_arn: ((kms_key_arn))
`

func (e Executor) DirectorInterpolate(interpolateInput InterpolateInput) (InterpolateOutput, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		//not tested
		return InterpolateOutput{}, err
	}

	var directorSetupFiles = map[string][]byte{
		"deployment-vars.yml":                    []byte(interpolateInput.DeploymentVars),
		"user-ops-file.yml":                      []byte(interpolateInput.OpsFile),
		"bosh.yml":                               MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/bosh.yml"),
		"cpi.yml":                                MustAsset(fmt.Sprintf("vendor/github.com/cloudfoundry/bosh-deployment/%s/cpi.yml", interpolateInput.IAAS)),
		"iam-instance-profile.yml":               MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/aws/iam-instance-profile.yml"),
		"use-managed-disks.yml":                  MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/azure/use-managed-disks.yml"),
		"gcp-bosh-director-ephemeral-ip-ops.yml": []byte(gcpBoshDirectorEphemeralIPOps),
		"aws-bosh-director-ephemeral-ip-ops.yml": []byte(awsBoshDirectorEphemeralIPOps),
		"aws-bosh-director-encrypt-disk-ops.yml": []byte(awsEncryptDiskOps),
		"azure-ssh-static-ip.yml":                []byte(azureSSHStaticIP),
		"jumpbox-user.yml":                       MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/jumpbox-user.yml"),
		"gcp-external-ip-not-recommended.yml":    MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/external-ip-not-recommended.yml"),
		"azure-external-ip-not-recommended.yml":  MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/external-ip-not-recommended.yml"),
		"aws-external-ip-not-recommended.yml":    MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/external-ip-with-registry-not-recommended.yml"),
		"uaa.yml":     MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/uaa.yml"),
		"credhub.yml": MustAsset("vendor/github.com/cloudfoundry/bosh-deployment/credhub.yml"),
	}

	if interpolateInput.Variables != "" {
		directorSetupFiles["variables.yml"] = []byte(interpolateInput.Variables)
	}

	for path, contents := range directorSetupFiles {
		err = e.writeFile(filepath.Join(tempDir, path), contents, os.ModePerm)
		if err != nil {
			//not tested
			return InterpolateOutput{}, err
		}
	}

	var args = []string{
		"interpolate", filepath.Join(tempDir, "bosh.yml"),
		"--var-errs",
		"--vars-store", filepath.Join(tempDir, "variables.yml"),
		"--vars-file", filepath.Join(tempDir, "deployment-vars.yml"),
		"-o", filepath.Join(tempDir, "cpi.yml"),
	}

	// if interpolateInput.IAAS == "azure" {
	// 	args = append(args, "-o", filepath.Join(tempDir, "use-managed-disks.yml"))
	// }

	if interpolateInput.JumpboxDeploymentVars == "" {
		args = append(args,
			"-o", filepath.Join(tempDir, "jumpbox-user.yml"),
		)

		switch interpolateInput.IAAS {
		case "gcp":
			args = append(args, "-o", filepath.Join(tempDir, "gcp-external-ip-not-recommended.yml"))
		case "aws":
			args = append(args, "-o", filepath.Join(tempDir, "aws-external-ip-not-recommended.yml"))
		case "azure":
			args = append(args, "-o", filepath.Join(tempDir, "azure-external-ip-not-recommended.yml"))
			args = append(args, "-o", filepath.Join(tempDir, "azure-ssh-static-ip.yml"))
		}
	} else {
		args = append(args,
			"-o", filepath.Join(tempDir, "uaa.yml"),
			"-o", filepath.Join(tempDir, "credhub.yml"),
		)
		switch interpolateInput.IAAS {
		case "gcp":
			args = append(args, "-o", filepath.Join(tempDir, "gcp-bosh-director-ephemeral-ip-ops.yml"))
		case "aws":
			args = append(args, "-o", filepath.Join(tempDir, "aws-bosh-director-ephemeral-ip-ops.yml"))
		}
	}

	if interpolateInput.IAAS == "aws" {
		args = append(args,
			"-o", filepath.Join(tempDir, "iam-instance-profile.yml"),
			"-o", filepath.Join(tempDir, "aws-bosh-director-encrypt-disk-ops.yml"),
		)
	}

	buffer := bytes.NewBuffer([]byte{})
	err = e.command.Run(buffer, tempDir, args)
	if err != nil {
		return InterpolateOutput{}, err
	}

	if interpolateInput.OpsFile != "" {
		err = e.writeFile(filepath.Join(tempDir, "bosh.yml"), buffer.Bytes(), os.ModePerm)
		if err != nil {
			//not tested
			return InterpolateOutput{}, err
		}

		args = []string{
			"interpolate", filepath.Join(tempDir, "bosh.yml"),
			"--var-errs",
			"--vars-store", filepath.Join(tempDir, "variables.yml"),
			"--vars-file", filepath.Join(tempDir, "deployment-vars.yml"),
			"-o", filepath.Join(tempDir, "user-ops-file.yml"),
		}

		buffer = bytes.NewBuffer([]byte{})
		err = e.command.Run(buffer, tempDir, args)
		if err != nil {
			return InterpolateOutput{}, err
		}
	}

	varsStore, err := e.readFile(filepath.Join(tempDir, "variables.yml"))
	if err != nil {
		return InterpolateOutput{}, err
	}

	return InterpolateOutput{
		Variables: string(varsStore),
		Manifest:  buffer.String(),
	}, nil
}

func (e Executor) CreateEnv(createEnvInput CreateEnvInput) (CreateEnvOutput, error) {
	tempDir, err := e.writePreviousFiles(createEnvInput.State, createEnvInput.Variables, createEnvInput.Manifest)
	if err != nil {
		return CreateEnvOutput{}, err
	}

	statePath := fmt.Sprintf("%s/state.json", tempDir)
	variablesPath := fmt.Sprintf("%s/variables.yml", tempDir)
	manifestPath := filepath.Join(tempDir, "manifest.yml")

	args := []string{
		"create-env", manifestPath,
		"--vars-store", variablesPath,
		"--state", statePath,
	}

	err = e.command.Run(os.Stdout, tempDir, args)
	if err != nil {
		state, readErr := e.readBOSHState(statePath)
		if readErr != nil {
			errorList := helpers.Errors{}
			errorList.Add(err)
			errorList.Add(readErr)
			return CreateEnvOutput{}, errorList
		}

		return CreateEnvOutput{}, NewCreateEnvError(state, err)
	}

	state, err := e.readBOSHState(statePath)
	if err != nil {
		return CreateEnvOutput{}, err
	}

	return CreateEnvOutput{
		State: state,
	}, nil
}

func (e Executor) readBOSHState(statePath string) (map[string]interface{}, error) {
	stateContents, err := e.readFile(statePath)
	if err != nil {
		return map[string]interface{}{}, err
	}

	var state map[string]interface{}
	err = e.unmarshalJSON(stateContents, &state)
	if err != nil {
		return map[string]interface{}{}, err
	}

	return state, nil
}

func (e Executor) DeleteEnv(deleteEnvInput DeleteEnvInput) error {
	tempDir, err := e.writePreviousFiles(deleteEnvInput.State, deleteEnvInput.Variables, deleteEnvInput.Manifest)
	if err != nil {
		return err
	}

	statePath := fmt.Sprintf("%s/state.json", tempDir)
	variablesPath := fmt.Sprintf("%s/variables.yml", tempDir)
	boshManifestPath := filepath.Join(tempDir, "manifest.yml")

	args := []string{
		"delete-env", boshManifestPath,
		"--vars-store", variablesPath,
		"--state", statePath,
	}

	err = e.command.Run(os.Stdout, tempDir, args)
	if err != nil {
		state, readErr := e.readBOSHState(statePath)
		if readErr != nil {
			errorList := helpers.Errors{}
			errorList.Add(err)
			errorList.Add(readErr)
			return errorList
		}
		return NewDeleteEnvError(state, err)
	}

	return nil
}

func (e Executor) Version() (string, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		return "", err
	}

	args := []string{"-v"}

	buffer := bytes.NewBuffer([]byte{})
	err = e.command.Run(buffer, tempDir, args)
	if err != nil {
		return "", err
	}

	versionOutput := buffer.String()
	regex := regexp.MustCompile(`\d+.\d+.\d+`)

	version := regex.FindString(versionOutput)
	if version == "" {
		return "", NewBOSHVersionError(errors.New("BOSH version could not be parsed"))
	}

	return version, nil
}

func (e Executor) writePreviousFiles(state map[string]interface{}, variables, manifest string) (string, error) {
	tempDir, err := e.tempDir("", "")
	if err != nil {
		return "", err
	}

	statePath := fmt.Sprintf("%s/state.json", tempDir)
	variablesPath := fmt.Sprintf("%s/variables.yml", tempDir)
	boshManifestPath := filepath.Join(tempDir, "manifest.yml")

	if state != nil {
		boshStateContents, err := e.marshalJSON(state)
		if err != nil {
			return "", err
		}
		err = e.writeFile(statePath, boshStateContents, os.ModePerm)
		if err != nil {
			return "", err
		}
	}

	err = e.writeFile(variablesPath, []byte(variables), os.ModePerm)
	if err != nil {
		// not tested
		return "", err
	}

	err = e.writeFile(boshManifestPath, []byte(manifest), os.ModePerm)
	if err != nil {
		// not tested
		return "", err
	}

	return tempDir, nil
}
<|endoftext|>


<commit_before>package mms

import (
	"net/http"
	"testing"
	"time"

	messagebird "github.com/messagebird/go-rest-api/v9"
	"github.com/messagebird/go-rest-api/v9/internal/mbtest"
	"github.com/stretchr/testify/assert"
)

func TestMain(m *testing.M) {
	mbtest.EnableServer(m)
}

func TestCreate(t *testing.T) {
	mbtest.WillReturnTestdata(t, "mmsMessageObject.json", http.StatusOK)
	client := mbtest.Client(t)

	scheduledDateTime := time.Now()

	req := &CreateRequest{
		Body:              "Hello World",
		MediaUrls:         []string{"https://media.giphy.com/media/Vuw9m5wXviFIQ/giphy.gif", "https://media.giphy.com/media/pxy9QQUMF0glq/giphy.gif"},
		Subject:           "TestSubject",
		Reference:         "TestReference",
		ScheduledDatetime: &scheduledDateTime,
	}

	message, err := Create(client, req)

	assert.NoError(t, err)
	assert.Equal(t, "6d9e7100b1f9406c81a3c303c30ccf05", message.ID)
	assert.Equal(t, "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05", message.HRef)
	assert.Equal(t, "mt", message.Direction)
	assert.Equal(t, "TestName", message.Originator)
	assert.Equal(t, "Hello World", message.Body)
	assert.Equal(t, "https://media.giphy.com/media/Vuw9m5wXviFIQ/giphy.gif", message.MediaUrls[0])
	assert.Equal(t, "https://media.giphy.com/media/pxy9QQUMF0glq/giphy.gif", message.MediaUrls[1])
	assert.Equal(t, "TestReference", message.Reference)
	assert.Equal(t, "TestSubject", message.Subject)
	assert.Nil(t, message.ScheduledDatetime)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.CreatedDatetime.Format(time.RFC3339))
	assert.Equal(t, 1, message.Recipients.TotalCount)
	assert.Equal(t, 1, message.Recipients.TotalSentCount)
	assert.Equal(t, int64(31612345678), message.Recipients.Items[0].Recipient)
	assert.Equal(t, "sent", message.Recipients.Items[0].Status)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.Recipients.Items[0].StatusDatetime.Format(time.RFC3339))

	_, ok := err.(messagebird.ErrorResponse)
	assert.False(t, ok)
}

func TestRead(t *testing.T) {
	mbtest.WillReturnTestdata(t, "mmsMessageObject.json", http.StatusOK)
	client := mbtest.Client(t)

	message, err := Read(client, "6d9e7100b1f9406c81a3c303c30ccf05")

	assert.NoError(t, err)
	assert.Equal(t, "6d9e7100b1f9406c81a3c303c30ccf05", message.ID)
	assert.Equal(t, "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05", message.HRef)
	assert.Equal(t, "mt", message.Direction)
	assert.Equal(t, "TestName", message.Originator)
	assert.Equal(t, "Hello World", message.Body)
	assert.Equal(t, "https://media.giphy.com/media/Vuw9m5wXviFIQ/giphy.gif", message.MediaUrls[0])
	assert.Equal(t, "https://media.giphy.com/media/pxy9QQUMF0glq/giphy.gif", message.MediaUrls[1])
	assert.Equal(t, "TestReference", message.Reference)
	assert.Equal(t, "TestSubject", message.Subject)
	assert.Nil(t, message.ScheduledDatetime)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.CreatedDatetime.Format(time.RFC3339))
	assert.Equal(t, 1, message.Recipients.TotalCount)
	assert.Equal(t, 1, message.Recipients.TotalSentCount)
	assert.Equal(t, int64(31612345678), message.Recipients.Items[0].Recipient)
	assert.Equal(t, "sent", message.Recipients.Items[0].Status)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.Recipients.Items[0].StatusDatetime.Format(time.RFC3339))

	_, ok := err.(messagebird.ErrorResponse)
	assert.False(t, ok)
}
<commit_msg>add tests for mms create request<commit_after>package mms

import (
	"net/http"
	"testing"
	"time"

	messagebird "github.com/messagebird/go-rest-api/v9"
	"github.com/messagebird/go-rest-api/v9/internal/mbtest"
	"github.com/stretchr/testify/assert"
)

func TestMain(m *testing.M) {
	mbtest.EnableServer(m)
}

func TestCreate(t *testing.T) {
	mbtest.WillReturnTestdata(t, "mmsMessageObject.json", http.StatusOK)
	client := mbtest.Client(t)

	scheduledDateTime := time.Now()

	req := &CreateRequest{
		Body:              "Hello World",
		MediaUrls:         []string{"https://media.giphy.com/media/Vuw9m5wXviFIQ/giphy.gif", "https://media.giphy.com/media/pxy9QQUMF0glq/giphy.gif"},
		Subject:           "TestSubject",
		Reference:         "TestReference",
		ScheduledDatetime: &scheduledDateTime,
	}

	message, err := Create(client, req)

	assert.NoError(t, err)
	assert.Equal(t, "6d9e7100b1f9406c81a3c303c30ccf05", message.ID)
	assert.Equal(t, "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05", message.HRef)
	assert.Equal(t, "mt", message.Direction)
	assert.Equal(t, "TestName", message.Originator)
	assert.Equal(t, "Hello World", message.Body)
	assert.Equal(t, "https://media.giphy.com/media/Vuw9m5wXviFIQ/giphy.gif", message.MediaUrls[0])
	assert.Equal(t, "https://media.giphy.com/media/pxy9QQUMF0glq/giphy.gif", message.MediaUrls[1])
	assert.Equal(t, "TestReference", message.Reference)
	assert.Equal(t, "TestSubject", message.Subject)
	assert.Nil(t, message.ScheduledDatetime)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.CreatedDatetime.Format(time.RFC3339))
	assert.Equal(t, 1, message.Recipients.TotalCount)
	assert.Equal(t, 1, message.Recipients.TotalSentCount)
	assert.Equal(t, int64(31612345678), message.Recipients.Items[0].Recipient)
	assert.Equal(t, "sent", message.Recipients.Items[0].Status)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.Recipients.Items[0].StatusDatetime.Format(time.RFC3339))

	_, ok := err.(messagebird.ErrorResponse)
	assert.False(t, ok)
}

func TestCreateNilRequest(t *testing.T) {
	mbtest.WillReturnTestdata(t, "mmsMessageObject.json", http.StatusOK)
	client := mbtest.Client(t)

	message, err := Create(client, nil)

	assert.Nil(t, message)
	assert.Error(t, err)
	assert.EqualError(t, err, "create request should not be nil")
}

func TestCreateWithoutBodyAndMediaUrls(t *testing.T) {
	mbtest.WillReturnTestdata(t, "mmsMessageObject.json", http.StatusOK)
	client := mbtest.Client(t)

	scheduledDateTime := time.Now()

	req := &CreateRequest{
		Subject:           "TestSubject",
		Reference:         "TestReference",
		ScheduledDatetime: &scheduledDateTime,
	}

	message, err := Create(client, req)

	assert.Nil(t, message)
	assert.Error(t, err)
	assert.EqualError(t, err, "body or mediaUrls is required")
}

func TestRead(t *testing.T) {
	mbtest.WillReturnTestdata(t, "mmsMessageObject.json", http.StatusOK)
	client := mbtest.Client(t)

	message, err := Read(client, "6d9e7100b1f9406c81a3c303c30ccf05")

	assert.NoError(t, err)
	assert.Equal(t, "6d9e7100b1f9406c81a3c303c30ccf05", message.ID)
	assert.Equal(t, "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05", message.HRef)
	assert.Equal(t, "mt", message.Direction)
	assert.Equal(t, "TestName", message.Originator)
	assert.Equal(t, "Hello World", message.Body)
	assert.Equal(t, "https://media.giphy.com/media/Vuw9m5wXviFIQ/giphy.gif", message.MediaUrls[0])
	assert.Equal(t, "https://media.giphy.com/media/pxy9QQUMF0glq/giphy.gif", message.MediaUrls[1])
	assert.Equal(t, "TestReference", message.Reference)
	assert.Equal(t, "TestSubject", message.Subject)
	assert.Nil(t, message.ScheduledDatetime)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.CreatedDatetime.Format(time.RFC3339))
	assert.Equal(t, 1, message.Recipients.TotalCount)
	assert.Equal(t, 1, message.Recipients.TotalSentCount)
	assert.Equal(t, int64(31612345678), message.Recipients.Items[0].Recipient)
	assert.Equal(t, "sent", message.Recipients.Items[0].Status)
	assert.Equal(t, "2022-05-20T12:50:28Z", message.Recipients.Items[0].StatusDatetime.Format(time.RFC3339))

	_, ok := err.(messagebird.ErrorResponse)
	assert.False(t, ok)
}
<|endoftext|>


<commit_before>// +build linux

package qemuguesttools

import (
	"bytes"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"strings"
	"sync"
	"testing"

	"github.com/taskcluster/taskcluster-worker/engines/qemu/metaservice"
	"github.com/taskcluster/taskcluster-worker/runtime"
	"github.com/taskcluster/taskcluster-worker/runtime/atomics"
	"github.com/taskcluster/taskcluster-worker/runtime/mocks"
)

func TestGuestToolsSuccess(t *testing.T) {
	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
		Monitor:          mocks.NewMockMonitor(true),
	}

	// Setup a new MetaService
	logTask := bytes.NewBuffer(nil)
	result := false
	var resolved atomics.Once
	s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && true"}, map[string]string{
		"TEST_TEXT": "Hello world",
	}, logTask, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))
	g.Run()

	// Check the state
	resolved.Wait()
	if !result {
		t.Error("Expected the metadata to get successful result")
	}
	if !strings.Contains(logTask.String(), "Hello world") {
		t.Error("Got unexpected taskLog: '", logTask.String(), "'")
	}
}

func TestGuestToolsFailed(t *testing.T) {
	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
		Monitor:          mocks.NewMockMonitor(true),
	}

	// Setup a new MetaService
	logTask := bytes.NewBuffer(nil)
	result := false
	var resolved atomics.Once
	s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && false"}, map[string]string{
		"TEST_TEXT": "Hello world",
	}, logTask, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))
	g.Run()

	// Check the state
	resolved.Wait()
	if result {
		t.Error("Expected the metadata to get failed result")
	}
	if !strings.Contains(logTask.String(), "Hello world") {
		t.Error("Got unexpected taskLog: '", logTask.String(), "'")
	}
}

func TestGuestToolsLiveLog(t *testing.T) {
	nowReady := sync.WaitGroup{}
	nowReady.Add(1)
	ps := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		debug("Waiting for ready-now to be readable in log")
		nowReady.Wait()
		debug("replying: request-ok")

		w.WriteHeader(http.StatusOK)
		w.Write([]byte("request-ok"))
	}))

	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
	}

	// Setup a new MetaService
	reader, writer := io.Pipe()
	result := false
	var resolved atomics.Once
	s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && curl -s " + ps.URL}, map[string]string{
		"TEST_TEXT": "ready-now",
	}, writer, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Wait for
	logTask := bytes.NewBuffer(nil)
	logDone := sync.WaitGroup{}
	logDone.Add(1)
	go func() {
		b := make([]byte, 1)
		for !strings.Contains(logTask.String(), "ready-now") {
			n, err := reader.Read(b)
			logTask.Write(b[:n])
			if err != nil {
				panic("Unexpected error")
			}
		}
		nowReady.Done()
		io.Copy(logTask, reader)
		logDone.Done()
	}()

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))
	g.Run()
	writer.Close()
	logDone.Wait()

	// Check the state
	resolved.Wait()
	if !result {
		t.Error("Expected the metadata to get successful result")
	}
	if !strings.Contains(logTask.String(), "request-ok") {
		t.Error("Got unexpected taskLog: '", logTask.String(), "'")
	}
}

func TestGuestToolsKill(t *testing.T) {
	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
		Monitor:          mocks.NewMockMonitor(true),
	}

	// Setup a new MetaService
	reader, writer := io.Pipe()
	result := false
	var resolved atomics.Once
	s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && sleep 20 && true"}, map[string]string{
		"TEST_TEXT": "kill-me-now",
	}, writer, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	defer s.StopPollers() // HACK: stop pollers or they will hang
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Wait for
	logTask := bytes.NewBuffer(nil)
	logDone := sync.WaitGroup{}
	logDone.Add(1)
	go func() {
		b := make([]byte, 1)
		for !strings.Contains(logTask.String(), "kill-me-now") {
			n, err := reader.Read(b)
			logTask.Write(b[:n])
			if err != nil {
				panic("Unexpected error")
			}
		}
		debug("reached 'kill-me-now'")
		go func() {
			if err := s.KillProcess(); err != nil {
				panic(err)
			}
		}()
		io.Copy(logTask, reader)
		logDone.Done()
	}()

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))

	// start processing actions
	go g.ProcessActions()
	defer g.StopProcessingActions()

	g.Run()
	writer.Close()
	logDone.Wait()

	// Check the state
	resolved.Wait()
	if result {
		t.Error("Expected the metadata to get failed result")
	}
}
<commit_msg>Fixed Success, Failed and livelog tests for qemu-guest-tools on windows<commit_after>// +build linux windows

package qemuguesttools

import (
	"bytes"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	rt "runtime"
	"strings"
	"sync"
	"testing"

	"github.com/taskcluster/taskcluster-worker/engines/qemu/metaservice"
	"github.com/taskcluster/taskcluster-worker/runtime"
	"github.com/taskcluster/taskcluster-worker/runtime/atomics"
	"github.com/taskcluster/taskcluster-worker/runtime/mocks"
)

func TestGuestToolsSuccess(t *testing.T) {
	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
		Monitor:          mocks.NewMockMonitor(true),
	}

	// platform specific hello world command
	helloWorldCommand := []string{"sh", "-c", "echo \"$TEST_TEXT\" && true"}
	if rt.GOOS == "windows" {
		helloWorldCommand = []string{`c:\Windows\system32\cmd.exe`, "/C", "echo %TEST_TEXT% && exit 0"}
	}

	// Setup a new MetaService
	logTask := bytes.NewBuffer(nil)
	result := false
	var resolved atomics.Once
	s := metaservice.New(helloWorldCommand, map[string]string{
		"TEST_TEXT": "Hello world",
	}, logTask, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))
	g.Run()

	// Check the state
	resolved.Wait()
	if !result {
		t.Error("Expected the metadata to get successful result")
	}
	debug(logTask.String())
	if !strings.Contains(logTask.String(), "Hello world") {
		t.Error("Got unexpected taskLog: '", logTask.String(), "'")
	}
}

func TestGuestToolsFailed(t *testing.T) {
	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
		Monitor:          mocks.NewMockMonitor(true),
	}

	// platform specific hello world command
	helloWorldCommand := []string{"sh", "-c", "echo \"$TEST_TEXT\" && false"}
	if rt.GOOS == "windows" {
		helloWorldCommand = []string{`c:\Windows\system32\cmd.exe`, "/C", "echo %TEST_TEXT% && exit 1"}
	}

	// Setup a new MetaService
	logTask := bytes.NewBuffer(nil)
	result := false
	var resolved atomics.Once
	s := metaservice.New(helloWorldCommand, map[string]string{
		"TEST_TEXT": "Hello world",
	}, logTask, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))
	g.Run()

	// Check the state
	resolved.Wait()
	if result {
		t.Error("Expected the metadata to get failed result")
	}
	if !strings.Contains(logTask.String(), "Hello world") {
		t.Error("Got unexpected taskLog: '", logTask.String(), "'")
	}
}

func TestGuestToolsLiveLog(t *testing.T) {
	nowReady := sync.WaitGroup{}
	nowReady.Add(1)
	ps := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		debug("Waiting for ready-now to be readable in log")
		nowReady.Wait()
		debug("replying: request-ok")

		w.WriteHeader(http.StatusOK)
		w.Write([]byte("request-ok"))
	}))

	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
	}

	// platform specific hello world command
	command := []string{"sh", "-c", "echo \"$TEST_TEXT\" && curl -s " + ps.URL}
	if rt.GOOS == "windows" {
		command = []string{
			`c:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`, "-Command",
			`(get-item env:TEST_TEXT).Value ; (new-object System.Net.WebClient).DownloadString('` + ps.URL + `')`,
		}
	}

	// Setup a new MetaService
	reader, writer := io.Pipe()
	result := false
	var resolved atomics.Once
	s := metaservice.New(command, map[string]string{
		"TEST_TEXT": "ready-now",
	}, writer, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Wait for
	logTask := bytes.NewBuffer(nil)
	logDone := sync.WaitGroup{}
	logDone.Add(1)
	go func() {
		b := make([]byte, 1)
		for !strings.Contains(logTask.String(), "ready-now") {
			n, err := reader.Read(b)
			logTask.Write(b[:n])
			if err != nil {
				panic("Unexpected error")
			}
		}
		nowReady.Done()
		io.Copy(logTask, reader)
		logDone.Done()
	}()

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))
	g.Run()
	writer.Close()
	logDone.Wait()

	// Check the state
	resolved.Wait()
	if !result {
		t.Error("Expected the metadata to get successful result")
	}
	if !strings.Contains(logTask.String(), "request-ok") {
		t.Error("Got unexpected taskLog: '", logTask.String(), "'")
	}
}

func TestGuestToolsKill(t *testing.T) {
	// Create temporary storage
	storage, err := runtime.NewTemporaryStorage(os.TempDir())
	if err != nil {
		panic("Failed to create TemporaryStorage")
	}
	environment := &runtime.Environment{
		TemporaryStorage: storage,
		Monitor:          mocks.NewMockMonitor(true),
	}

	// Setup a new MetaService
	reader, writer := io.Pipe()
	result := false
	var resolved atomics.Once
	s := metaservice.New([]string{"sh", "-c", "echo \"$TEST_TEXT\" && sleep 20 && true"}, map[string]string{
		"TEST_TEXT": "kill-me-now",
	}, writer, func(r bool) {
		if !resolved.Do(func() { result = r }) {
			panic("It shouldn't be possible to resolve twice")
		}
	}, environment)

	// Create http server for testing
	ts := httptest.NewServer(s)
	defer ts.Close()
	defer s.StopPollers() // HACK: stop pollers or they will hang
	u, err := url.Parse(ts.URL)
	if err != nil {
		panic("Expected a url we can parse")
	}

	// Wait for
	logTask := bytes.NewBuffer(nil)
	logDone := sync.WaitGroup{}
	logDone.Add(1)
	go func() {
		b := make([]byte, 1)
		for !strings.Contains(logTask.String(), "kill-me-now") {
			n, err := reader.Read(b)
			logTask.Write(b[:n])
			if err != nil {
				panic("Unexpected error")
			}
		}
		debug("reached 'kill-me-now'")
		go func() {
			if err := s.KillProcess(); err != nil {
				panic(err)
			}
		}()
		io.Copy(logTask, reader)
		logDone.Done()
	}()

	// Create an run guest-tools
	g := new(u.Host, mocks.NewMockMonitor(true))

	// start processing actions
	go g.ProcessActions()
	defer g.StopProcessingActions()

	g.Run()
	writer.Close()
	logDone.Wait()

	// Check the state
	resolved.Wait()
	if result {
		t.Error("Expected the metadata to get failed result")
	}
}
<|endoftext|>


<commit_before>package udf

import (
	"errors"
	"fmt"
	"pfi/sensorbee/sensorbee/core"
	"pfi/sensorbee/sensorbee/data"
	"reflect"
)

// UDSFCreator creates a new UDSF instance.
type UDSFCreator interface {
	// CreateUDSF returns a new UDSF instance. UDSF can be configured (via decl)
	// to accept inputs from multiple existing streams. It also receives
	// arguments passed to the UDSF in a BQL statement. The caller will call
	// UDSF.Terminate when the UDSF becomes unnecessary.
	//
	// UDSF must have at least one input from existing stream. A UDSF having no
	// input should be implemented as core.Source and created via CREATE SOURCE
	// statement. Creating a UDSF which acts like a Source might be going to
	// be supported in the future version.
	CreateUDSF(ctx *core.Context, decl UDSFDeclarer, args ...data.Value) (UDSF, error)

	// Accept returns true if the UDSF supports the given arity.
	Accept(arity int) bool
}

// ConvertToUDSFCreator converts a function to a UDSFCreator.
func ConvertToUDSFCreator(function interface{}) (UDSFCreator, error) {
	t := reflect.TypeOf(function)
	if t.Kind() != reflect.Func {
		return nil, errors.New("the argument must be a function")
	}

	g := &genericUDSFCreator{
		function:   reflect.ValueOf(function),
		hasContext: genericFuncHasContext(t),
		variadic:   t.IsVariadic(),
		arity:      t.NumIn(),
	}

	if g.hasContext {
		g.arity--
	}
	if g.arity == 0 {
		return nil, errors.New("the function must receive UDSFDeclarer as the first argument")
	}
	if c := t.In(t.NumIn() - g.arity); !reflect.TypeOf(func(UDSFDeclarer) {}).In(0).AssignableTo(c) {
		if g.hasContext {
			return nil, errors.New("the function must receive UDSFDeclarer as the second argument")
		}
		return nil, errors.New("the function must receive UDSFDeclarer as the first argument")
	}
	g.arity-- // UDSFDeclarer

	if err := checkUDSFCreatorFuncReturnTypes(t); err != nil {
		return nil, err
	}

	if convs, err := createGenericConverters(t, t.NumIn()-g.arity); err != nil {
		return nil, err
	} else {
		g.converters = convs
	}
	return g, nil
}

// MustConvertToUDSFCreator converts a function to a UDSFCreator.
// It panics if there is an error during conversion.
func MustConvertToUDSFCreator(function interface{}) UDSFCreator {
	f, err := ConvertToUDSFCreator(function)
	if err != nil {
		panic(err)
	}
	return f
}

func checkUDSFCreatorFuncReturnTypes(t reflect.Type) error {
	if t.NumOut() != 2 {
		return errors.New("the function must have (udf.UDSFCreator, error) as its return type")
	}
	if !t.Out(0).Implements(reflect.TypeOf(func(UDSF) {}).In(0)) {
		return fmt.Errorf("the first return value must be a UDSF: %v", t.Out(0))
	}
	if !t.Out(1).Implements(reflect.TypeOf(func(error) {}).In(0)) {
		return fmt.Errorf("the second return value must be an error: %v", t.Out(1))
	}
	return nil
}

type genericUDSFCreator struct {
	function   reflect.Value
	hasContext bool
	variadic   bool

	// arity is the number of arguments of UDSFCreator.CreateUDSF excluding
	// *core.Context and UDSFDeclarer.
	arity int

	converters []argumentConverter
}

func (g *genericUDSFCreator) CreateUDSF(ctx *core.Context, decl UDSFDeclarer, args ...data.Value) (UDSF, error) {
	if err := g.accept(len(args)); err != nil {
		return nil, err
	}

	in := make([]reflect.Value, 0, len(args)+2) // +2 for context and declarer
	if g.hasContext {
		in = append(in, reflect.ValueOf(ctx))
	}
	in = append(in, reflect.ValueOf(decl))

	variadicBegin := g.arity
	if g.variadic {
		variadicBegin--
	}

	for i := 0; i < variadicBegin; i++ {
		v, err := g.converters[i](args[i])
		if err != nil {
			return nil, err
		}
		in = append(in, reflect.ValueOf(v))
	}
	for i := variadicBegin; i < len(args); i++ {
		v, err := g.converters[len(g.converters)-1](args[i])
		if err != nil {
			return nil, err
		}
		in = append(in, reflect.ValueOf(v))
	}

	out := g.function.Call(in)
	if !out[1].IsNil() {
		return nil, out[1].Interface().(error)
	}
	if out[0].IsNil() {
		return nil, fmt.Errorf("the UDSF returned from the creator was nil")
	}
	return out[0].Interface().(UDSF), nil
}

func (g *genericUDSFCreator) Accept(arity int) bool {
	return g.accept(arity) == nil
}

func (g *genericUDSFCreator) accept(arity int) error {
	if arity < g.arity {
		if g.variadic && arity == g.arity-1 {
			// having no variadic parameter is ok.
		} else {
			return errors.New("insufficient number of arguments")
		}
	} else if arity != g.arity && !g.variadic {
		return errors.New("too many arguments")
	}
	return nil
}

// UDSF is a user-defined stream-generating function. While a regular UDF
// generates a tuple from an input tuple, UDSF can generate multiple tuples
// from a tuple or even drop tuples. It's also a little different from a
// "function". UDSF can have a state. Thus, a UDSF is created by UDSFCreator
// for each invocation in a BQL statement.
//
// UDSF doesn't have Init method because initialization should be done in
// UDSFCreator.CreateUDSF.
type UDSF interface {
	// Process sends a tuple to the UDSF. This method must not block and
	// return immediately after it finished processing the received tuple.
	Process(ctx *core.Context, t *core.Tuple, w core.Writer) error

	// Terminate terminates the UDSF. Resources allocated when the UDSF is
	// created by UDFSCreator should be released in this method.
	Terminate(ctx *core.Context) error
}

// UDSFDeclarer allow UDSFs to customize their behavior.
type UDSFDeclarer interface {
	// Input adds an input from an existing stream. A UDSF doesn't have to
	// have an input. For example, there can be a UDSF which generates
	// sequential numbers and doesn't depend on any stream.
	Input(name string, config *UDSFInputConfig) error

	// ListInputs returns all inputs declared by a UDSF. The caller can safely
	// modify the map returned from this method.
	ListInputs() map[string]*UDSFInputConfig
}

// UDSFInputConfig has input configuration parameters for UDSF.
type UDSFInputConfig struct {
	// InputName is a custom name attached to incoming tuples. If this name is
	// empty, "*" will be used.
	InputName string
}

type udsfDeclarer struct {
	inputs map[string]*UDSFInputConfig
}

func newUDSFDeclarer() *udsfDeclarer {
	return &udsfDeclarer{
		inputs: map[string]*UDSFInputConfig{},
	}
}

// NewUDSFDeclarer creates a new declarer.
func NewUDSFDeclarer() UDSFDeclarer {
	return newUDSFDeclarer()
}

func (d *udsfDeclarer) Input(name string, config *UDSFInputConfig) error {
	if _, ok := d.inputs[name]; ok {
		return fmt.Errorf("the input is already added: %v", name)
	}
	if config == nil {
		config = &UDSFInputConfig{}
	}
	d.inputs[name] = config
	return nil
}

func (d *udsfDeclarer) ListInputs() map[string]*UDSFInputConfig {
	m := make(map[string]*UDSFInputConfig, len(d.inputs))
	for i, c := range d.inputs {
		m[i] = c
	}
	return m
}
<commit_msg>Update godoc comments of UDSF to support the source mode<commit_after>package udf

import (
	"errors"
	"fmt"
	"pfi/sensorbee/sensorbee/core"
	"pfi/sensorbee/sensorbee/data"
	"reflect"
)

// UDSFCreator creates a new UDSF instance.
type UDSFCreator interface {
	// CreateUDSF returns a new UDSF instance. UDSF can be configured (via decl)
	// to accept inputs from multiple existing streams. It also receives
	// arguments passed to the UDSF in a BQL statement. The caller will call
	// UDSF.Terminate when the UDSF becomes unnecessary.
	//
	// When a UDSF needs inputs from other sources or streams, call Input method
	// of UDSFDeclarer. For example let's assume there's a UDSF below:
	//
	//	func createMyUDSF(decl udf.UDSFDeclarer, input1, input2 string) (udf.UDSF, error) {
	//		...
	//		decl.Input(input1, &udf.UDSFInputConfig{
	//			InputName: "custom_input_name_1",
	//		})
	//		decl.Input(input2, &udf.UDSFInputConfig{
	//			InputName: "custom_input_name_2",
	//		})
	//		...
	//	}
	//
	//	func init() {
	//		udf.MustRegisterGlobalUDSFCreator("my_udsf",
	//			udf.MustConvertToUDSFCreator(createMyUDSF))
	//	}
	//
	// Then a user can specify input stream by the following statement:
	//
	//	CREATE STREAM stream1 AS SELECT ...;
	//	CREATE STREAM stream2 AS SELECT ...;
	//	CREATE STREAM join_by_udsf
	//	  SELECT RSTREAM * FROM my_udsf('stream1', 'stream2') [RANGE 1 TUPLES];
	//
	// In this example, my_udsf receives two streams: stream1 and stream2
	// created in advance.
	//
	// A UDSF doesn't have to have an input. For example, there can be a UDSF
	// which generates sequential numbers and doesn't depend on any stream.
	// Such UDSFs will be run as the source mode. See UDSF for more details.
	CreateUDSF(ctx *core.Context, decl UDSFDeclarer, args ...data.Value) (UDSF, error)

	// Accept returns true if the UDSF supports the given arity.
	Accept(arity int) bool
}

// ConvertToUDSFCreator converts a function to a UDSFCreator.
func ConvertToUDSFCreator(function interface{}) (UDSFCreator, error) {
	t := reflect.TypeOf(function)
	if t.Kind() != reflect.Func {
		return nil, errors.New("the argument must be a function")
	}

	g := &genericUDSFCreator{
		function:   reflect.ValueOf(function),
		hasContext: genericFuncHasContext(t),
		variadic:   t.IsVariadic(),
		arity:      t.NumIn(),
	}

	if g.hasContext {
		g.arity--
	}
	if g.arity == 0 {
		return nil, errors.New("the function must receive UDSFDeclarer as the first argument")
	}
	if c := t.In(t.NumIn() - g.arity); !reflect.TypeOf(func(UDSFDeclarer) {}).In(0).AssignableTo(c) {
		if g.hasContext {
			return nil, errors.New("the function must receive UDSFDeclarer as the second argument")
		}
		return nil, errors.New("the function must receive UDSFDeclarer as the first argument")
	}
	g.arity-- // UDSFDeclarer

	if err := checkUDSFCreatorFuncReturnTypes(t); err != nil {
		return nil, err
	}

	if convs, err := createGenericConverters(t, t.NumIn()-g.arity); err != nil {
		return nil, err
	} else {
		g.converters = convs
	}
	return g, nil
}

// MustConvertToUDSFCreator converts a function to a UDSFCreator.
// It panics if there is an error during conversion.
func MustConvertToUDSFCreator(function interface{}) UDSFCreator {
	f, err := ConvertToUDSFCreator(function)
	if err != nil {
		panic(err)
	}
	return f
}

func checkUDSFCreatorFuncReturnTypes(t reflect.Type) error {
	if t.NumOut() != 2 {
		return errors.New("the function must have (udf.UDSFCreator, error) as its return type")
	}
	if !t.Out(0).Implements(reflect.TypeOf(func(UDSF) {}).In(0)) {
		return fmt.Errorf("the first return value must be a UDSF: %v", t.Out(0))
	}
	if !t.Out(1).Implements(reflect.TypeOf(func(error) {}).In(0)) {
		return fmt.Errorf("the second return value must be an error: %v", t.Out(1))
	}
	return nil
}

type genericUDSFCreator struct {
	function   reflect.Value
	hasContext bool
	variadic   bool

	// arity is the number of arguments of UDSFCreator.CreateUDSF excluding
	// *core.Context and UDSFDeclarer.
	arity int

	converters []argumentConverter
}

func (g *genericUDSFCreator) CreateUDSF(ctx *core.Context, decl UDSFDeclarer, args ...data.Value) (UDSF, error) {
	if err := g.accept(len(args)); err != nil {
		return nil, err
	}

	in := make([]reflect.Value, 0, len(args)+2) // +2 for context and declarer
	if g.hasContext {
		in = append(in, reflect.ValueOf(ctx))
	}
	in = append(in, reflect.ValueOf(decl))

	variadicBegin := g.arity
	if g.variadic {
		variadicBegin--
	}

	for i := 0; i < variadicBegin; i++ {
		v, err := g.converters[i](args[i])
		if err != nil {
			return nil, err
		}
		in = append(in, reflect.ValueOf(v))
	}
	for i := variadicBegin; i < len(args); i++ {
		v, err := g.converters[len(g.converters)-1](args[i])
		if err != nil {
			return nil, err
		}
		in = append(in, reflect.ValueOf(v))
	}

	out := g.function.Call(in)
	if !out[1].IsNil() {
		return nil, out[1].Interface().(error)
	}
	if out[0].IsNil() {
		return nil, fmt.Errorf("the UDSF returned from the creator was nil")
	}
	return out[0].Interface().(UDSF), nil
}

func (g *genericUDSFCreator) Accept(arity int) bool {
	return g.accept(arity) == nil
}

func (g *genericUDSFCreator) accept(arity int) error {
	if arity < g.arity {
		if g.variadic && arity == g.arity-1 {
			// having no variadic parameter is ok.
		} else {
			return errors.New("insufficient number of arguments")
		}
	} else if arity != g.arity && !g.variadic {
		return errors.New("too many arguments")
	}
	return nil
}

// UDSF is a user-defined stream-generating function. While a regular UDF
// generates a tuple from an input tuple, UDSF can generate multiple tuples
// from a tuple or even drop tuples. It's also a little different from a
// "function". UDSF can have a state. Thus, a UDSF is created by UDSFCreator
// for each invocation in a BQL statement.
//
// UDSF doesn't have Init method because initialization should be done in
// UDSFCreator.CreateUDSF.
//
// There're two kinds of processing modes of UDSFs: stream mode and source mode.
// A UDSF is processed in the stream mode when it has at least one input stream.
// Otherwise, the UDSF is processed in the source mode. Process and Terminate
// methods change their behavior based on the mode. See documentation of each
// method for details.
type UDSF interface {
	// Process sends a tuple to the UDSF.
	//
	// When the UDSF is running in the stream mode, this method must not block
	// and return immediately after it finished processing the received tuple.
	// It is called everytime a tuple is received from streams. It behaves just
	// like core.Box.
	//
	// When the UDSF is running in the source mode, this method is only called
	// once. It can block until it generates all tuples or Terminate method
	// is called. A tuple passed to this method in the source mode doesn't
	// contain anything meaningful. It behaves like core.Source although the
	// interface is like core.Box.
	Process(ctx *core.Context, t *core.Tuple, w core.Writer) error

	// Terminate terminates the UDSF. Resources allocated when the UDSF is
	// created by UDFSCreator should be released in this method. Also, when
	// the UDSF is running in the source mode, Process method must return as
	// soon as Terminate is called.
	Terminate(ctx *core.Context) error
}

// UDSFDeclarer allow UDSFs to customize their behavior.
type UDSFDeclarer interface {
	// Input adds an input from an existing stream.
	Input(name string, config *UDSFInputConfig) error

	// ListInputs returns all inputs declared by a UDSF. The caller can safely
	// modify the map returned from this method.
	ListInputs() map[string]*UDSFInputConfig
}

// UDSFInputConfig has input configuration parameters for UDSF.
type UDSFInputConfig struct {
	// InputName is a custom name attached to incoming tuples. If this name is
	// empty, "*" will be used.
	InputName string
}

type udsfDeclarer struct {
	inputs map[string]*UDSFInputConfig
}

func newUDSFDeclarer() *udsfDeclarer {
	return &udsfDeclarer{
		inputs: map[string]*UDSFInputConfig{},
	}
}

// NewUDSFDeclarer creates a new declarer.
func NewUDSFDeclarer() UDSFDeclarer {
	return newUDSFDeclarer()
}

func (d *udsfDeclarer) Input(name string, config *UDSFInputConfig) error {
	if _, ok := d.inputs[name]; ok {
		return fmt.Errorf("the input is already added: %v", name)
	}
	if config == nil {
		config = &UDSFInputConfig{}
	}
	d.inputs[name] = config
	return nil
}

func (d *udsfDeclarer) ListInputs() map[string]*UDSFInputConfig {
	m := make(map[string]*UDSFInputConfig, len(d.inputs))
	for i, c := range d.inputs {
		m[i] = c
	}
	return m
}
<|endoftext|>


<commit_before>/*
	Package broker reserves jobs from beanstalkd, spawns worker processes,
	and manages the interaction between the two.
*/
package broker

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"syscall"
	"time"

	"github.com/kr/beanstalk"
)

type Broker struct {

	// Address of the beanstalkd server.
	Address string

	// The shell command to execute for each job.
	Cmd string

	// Tube name this broker will service.
	Tube string

	log     *log.Logger
	results chan<- *JobResult
}

type JobResult struct {

	// ExitStatus of the command; 0 for success.
	ExitStatus int

	// JobId from beanstalkd.
	JobId uint64

	// Stdout of the command.
	Stdout string
}

// New broker instance.
func New(address, tube string, cmd string, results chan<- *JobResult) (b Broker) {
	b.Address = address
	b.Tube = tube
	b.Cmd = cmd

	b.log = log.New(os.Stdout, fmt.Sprintf("[%s] ", tube), log.LstdFlags)
	b.results = results
	return
}

// Run connects to beanstalkd and starts broking.
// If ticks channel is present, one job is processed per tick.
func (b *Broker) Run(ticks chan bool) {
	b.log.Println("connecting to", b.Address)
	c, err := beanstalk.Dial("tcp", b.Address)
	if err != nil {
		panic(err)
	}

	b.log.Println("watching", b.Tube)
	ts := beanstalk.NewTubeSet(c, b.Tube)

	for {
		if ticks != nil {
			b.log.Println("waiting for tick")
			if _, ok := <-ticks; !ok {
				break
			}
		} else {
			b.log.Println("tickless")
		}

		id, body, err := ts.Reserve(24 * time.Hour)
		if err != nil {
			b.log.Fatal(err)
		}

		job := job{id: id, body: body, conn: c}

		result, err := b.handleJob(job, b.Cmd)
		if err != nil {
			log.Fatal(err)
		}

		b.log.Printf("job %d finished with exit(%d)", id, result.ExitStatus)
		if result.ExitStatus == 0 {
			ts.Conn.Delete(id)
		} else if result.ExitStatus == 1 {
			pri, err := job.priority()
			if err != nil {
				b.log.Fatal(err)
			}
			releaseErr := ts.Conn.Release(id, pri, 0)
			if releaseErr != nil {
				b.log.Fatal(releaseErr)
			}
		} else {
			log.Fatal(result.ExitStatus)
		}

		if b.results != nil {
			b.results <- result
		}
	}

	b.log.Println("broker finished")
}

func (b *Broker) handleJob(job job, shellCmd string) (*JobResult, error) {

	result := &JobResult{JobId: job.id}

	cmd := exec.Command("/bin/bash", "-c", shellCmd)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	// write into stdin
	written, err := stdin.Write(job.body)
	if err == nil {
		b.log.Println(written, "bytes written")
	} else {
		return nil, err
	}
	stdin.Close()

	// read from stdout
	stdoutBuffer := new(bytes.Buffer)
	read, err := io.Copy(stdoutBuffer, stdout)
	if err == nil {
		b.log.Println(read, "bytes read")
	} else {
		return nil, err
	}

	err = cmd.Wait()

	if e1, ok := err.(*exec.ExitError); ok {
		result.ExitStatus = e1.Sys().(syscall.WaitStatus).ExitStatus()
	} else {
		result.ExitStatus = 0
	}

	result.Stdout = stdoutBuffer.String()

	return result, nil
}
<commit_msg>Broker.doTick() extracted from Broker.Run().<commit_after>/*
	Package broker reserves jobs from beanstalkd, spawns worker processes,
	and manages the interaction between the two.
*/
package broker

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"syscall"
	"time"

	"github.com/kr/beanstalk"
)

type Broker struct {

	// Address of the beanstalkd server.
	Address string

	// The shell command to execute for each job.
	Cmd string

	// Tube name this broker will service.
	Tube string

	log     *log.Logger
	results chan<- *JobResult
}

type JobResult struct {

	// ExitStatus of the command; 0 for success.
	ExitStatus int

	// JobId from beanstalkd.
	JobId uint64

	// Stdout of the command.
	Stdout string
}

// New broker instance.
func New(address, tube string, cmd string, results chan<- *JobResult) (b Broker) {
	b.Address = address
	b.Tube = tube
	b.Cmd = cmd

	b.log = log.New(os.Stdout, fmt.Sprintf("[%s] ", tube), log.LstdFlags)
	b.results = results
	return
}

// Run connects to beanstalkd and starts broking.
// If ticks channel is present, one job is processed per tick.
func (b *Broker) Run(ticks chan bool) {
	b.log.Println("connecting to", b.Address)
	conn, err := beanstalk.Dial("tcp", b.Address)
	if err != nil {
		panic(err)
	}

	b.log.Println("watching", b.Tube)
	ts := beanstalk.NewTubeSet(conn, b.Tube)

	for {
		if ticks != nil {
			b.log.Println("waiting for tick")
			if _, ok := <-ticks; !ok {
				break
			}
		} else {
			b.log.Println("tickless")
		}

		b.doTick(conn, ts)
	}

	b.log.Println("broker finished")
}

func (b *Broker) doTick(conn *beanstalk.Conn, ts *beanstalk.TubeSet) {
	id, body, err := ts.Reserve(24 * time.Hour)
	if err != nil {
		b.log.Fatal(err)
	}

	job := job{id: id, body: body, conn: conn}

	result, err := b.handleJob(job, b.Cmd)
	if err != nil {
		log.Fatal(err)
	}

	b.log.Printf("job %d finished with exit(%d)", id, result.ExitStatus)
	if result.ExitStatus == 0 {
		ts.Conn.Delete(id)
	} else if result.ExitStatus == 1 {
		pri, err := job.priority()
		if err != nil {
			b.log.Fatal(err)
		}
		releaseErr := ts.Conn.Release(id, pri, 0)
		if releaseErr != nil {
			b.log.Fatal(releaseErr)
		}
	} else {
		log.Fatal(result.ExitStatus)
	}

	if b.results != nil {
		b.results <- result
	}
}

func (b *Broker) handleJob(job job, shellCmd string) (*JobResult, error) {

	result := &JobResult{JobId: job.id}

	cmd := exec.Command("/bin/bash", "-c", shellCmd)

	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	// write into stdin
	written, err := stdin.Write(job.body)
	if err == nil {
		b.log.Println(written, "bytes written")
	} else {
		return nil, err
	}
	stdin.Close()

	// read from stdout
	stdoutBuffer := new(bytes.Buffer)
	read, err := io.Copy(stdoutBuffer, stdout)
	if err == nil {
		b.log.Println(read, "bytes read")
	} else {
		return nil, err
	}

	err = cmd.Wait()

	if e1, ok := err.(*exec.ExitError); ok {
		result.ExitStatus = e1.Sys().(syscall.WaitStatus).ExitStatus()
	} else {
		result.ExitStatus = 0
	}

	result.Stdout = stdoutBuffer.String()

	return result, nil
}
<|endoftext|>