<commit_before>package activity

import (
	"errors"
	"fmt"
	"github.com/antonienko/goandroid/device"
	"strings"
	"time"
)

// Activity struct represents activity subsystem for associated android device.
type Activity struct {
	dev device.Device // Device instance
}

// NewActivity method gives an initial
// ized instance of activity manager.
// It takes device.Device is a parameter and returns an instance of activity.Activity
// struct.
func NewActivity(dev device.Device) Activity {
	return Activity{dev: dev}
}

// StartActivity method launches a activity on device. See "am start" for more
// more details regarding this command. It takes canonilcal class name as its
// first parameter which defines the package and class name of the activity to
// be launched and a list of accepable options by am start command. This method
// requires root access on device and returns error if root access can not be
// granted. It also returns other adb related errors if something goes wrong.
//
// TODO Check if activity can not be launched or not found or other command
// related outputs.
func (am Activity) StartActivity(canonicalClass string, options ...string) error {
	err := am.dev.Root()
	if err != nil {
		return err
	}
	if len(options) > 0 {
		cmd := []string{}
		cmd = append(cmd, "start")
		cmd = append(cmd, options...)
		cmd = append(cmd, canonicalClass)
		_, err := am.dev.Shell("am", cmd...)
		if err != nil {
			return err
		}
	} else {
		_, err := am.dev.Shell("am", "start", canonicalClass)
		if err != nil {
			return err
		}
	}
	return nil
}

func (am Activity) StopPackage(packageName string) error {
	err := am.dev.Root()
	if err != nil {
		return err
	}
	_, err = am.dev.Shell("am", "force-stop", packageName)
	if err != nil {
		return err
	}
	return nil
}

// GetFocusedActivity method provides currently focused activity on device screen.
// It uses adb command "dumpsys acyivity" command to determine focused activity
// and extracts package name from the line "mFocusedActivity".
// It returens error if something went wrong on adb side or the method is unable to
// determine the activity canonical package name.
func (am Activity) GetFocusedActivity() (string, error) {
	ret, err := am.dev.Shell("dumpsys", "activity", "|", "grep", "mFocusedActivity:")
	if err != nil {
		return "", err
	}
	if !strings.Contains(ret, "mFocusedActivity:") {
		return "", errors.New("Unable to detect focused activity")
	}
	trimmed := strings.TrimSpace(ret)
	parts := strings.Split(trimmed, " ")
	if len(parts) < 3 {
		return "", errors.New("Unable to determine activity canonical package name from line : " + trimmed)
	}
	return parts[3], nil
}

// IsActivityFocused method checks if given activity is focused on device
// screen or not. If the currently focused activity canonical package contains
// given name then it returns true, and its not case sensitive in nature.
// It returns error if activity information can not be parsed or somethis else
// went wront in between.
func (am Activity) IsActivityFocused(name string) (bool, error) {
	activity, err := am.GetFocusedActivity()
	if err != nil {
		return false, err
	}
	if strings.Contains(strings.ToLower(activity), strings.ToLower(name)) {
		return true, nil
	}
	return false, nil
}

// WaitForActivityToFocus method waits for given activity name to be focused on
// device screen in given timeout period. If activity is not focused within timeout
// then error is returned. Also error is returned if something else went wrong such as
// adb error or current focused activity can not be determined. This method returns
// instantly when given activity is found to be focused on screen.
func (am Activity) WaitForActivityToFocus(name string, timeout int) error {
	startTime := time.Now()
	for {
		currentTime := time.Now()
		delta := currentTime.Sub(startTime)
		if delta.Seconds() >= float64(timeout) {
			break
		}
		stat, err := am.IsActivityFocused(name)
		if err != nil {
			return err
		}
		if stat {
			return nil
		}
	}
	return errors.New(fmt.Sprintf("Activity %s is not focused on screen within timeout of %d seconds", name, timeout))
}

func (am Activity) WaitForeverForActivity(name string, sampleRate time.Duration, control chan bool) error {
	for {
		select {
		case <-control:
			fmt.Println("Stop signal on waitForActivity")
			return errors.New(fmt.Sprintf("Couldn't see Activity %s before the stop signal", name))
		default:
			fmt.Printf("Waiting forever %s",sampleRate)
			stat, err := am.IsActivityFocused(name)
			if err != nil {
				return err
			}
			if stat {
				return nil
			}
			time.Sleep(sampleRate)
		}

	}
}
<commit_msg>Create wait for activity to focus interruptable<commit_after>package activity

import (
	"errors"
	"fmt"
	"github.com/antonienko/goandroid/device"
	"strings"
	"time"
)

// Activity struct represents activity subsystem for associated android device.
type Activity struct {
	dev device.Device // Device instance
}

// NewActivity method gives an initial
// ized instance of activity manager.
// It takes device.Device is a parameter and returns an instance of activity.Activity
// struct.
func NewActivity(dev device.Device) Activity {
	return Activity{dev: dev}
}

// StartActivity method launches a activity on device. See "am start" for more
// more details regarding this command. It takes canonilcal class name as its
// first parameter which defines the package and class name of the activity to
// be launched and a list of accepable options by am start command. This method
// requires root access on device and returns error if root access can not be
// granted. It also returns other adb related errors if something goes wrong.
//
// TODO Check if activity can not be launched or not found or other command
// related outputs.
func (am Activity) StartActivity(canonicalClass string, options ...string) error {
	err := am.dev.Root()
	if err != nil {
		return err
	}
	if len(options) > 0 {
		cmd := []string{}
		cmd = append(cmd, "start")
		cmd = append(cmd, options...)
		cmd = append(cmd, canonicalClass)
		_, err := am.dev.Shell("am", cmd...)
		if err != nil {
			return err
		}
	} else {
		_, err := am.dev.Shell("am", "start", canonicalClass)
		if err != nil {
			return err
		}
	}
	return nil
}

func (am Activity) StopPackage(packageName string) error {
	err := am.dev.Root()
	if err != nil {
		return err
	}
	_, err = am.dev.Shell("am", "force-stop", packageName)
	if err != nil {
		return err
	}
	return nil
}

// GetFocusedActivity method provides currently focused activity on device screen.
// It uses adb command "dumpsys acyivity" command to determine focused activity
// and extracts package name from the line "mFocusedActivity".
// It returens error if something went wrong on adb side or the method is unable to
// determine the activity canonical package name.
func (am Activity) GetFocusedActivity() (string, error) {
	ret, err := am.dev.Shell("dumpsys", "activity", "|", "grep", "mFocusedActivity:")
	if err != nil {
		return "", err
	}
	if !strings.Contains(ret, "mFocusedActivity:") {
		return "", errors.New("Unable to detect focused activity")
	}
	trimmed := strings.TrimSpace(ret)
	parts := strings.Split(trimmed, " ")
	if len(parts) < 3 {
		return "", errors.New("Unable to determine activity canonical package name from line : " + trimmed)
	}
	return parts[3], nil
}

// IsActivityFocused method checks if given activity is focused on device
// screen or not. If the currently focused activity canonical package contains
// given name then it returns true, and its not case sensitive in nature.
// It returns error if activity information can not be parsed or somethis else
// went wront in between.
func (am Activity) IsActivityFocused(name string) (bool, error) {
	activity, err := am.GetFocusedActivity()
	if err != nil {
		return false, err
	}
	if strings.Contains(strings.ToLower(activity), strings.ToLower(name)) {
		return true, nil
	}
	return false, nil
}

// WaitForActivityToFocus method waits for given activity name to be focused on
// device screen in given timeout period. If activity is not focused within timeout
// then error is returned. Also error is returned if something else went wrong such as
// adb error or current focused activity can not be determined. This method returns
// instantly when given activity is found to be focused on screen.
func (am Activity) WaitForActivityToFocus(name string, timeout int) error {
	startTime := time.Now()
	for {
		currentTime := time.Now()
		delta := currentTime.Sub(startTime)
		if delta.Seconds() >= float64(timeout) {
			break
		}
		stat, err := am.IsActivityFocused(name)
		if err != nil {
			return err
		}
		if stat {
			return nil
		}
	}
	return errors.New(fmt.Sprintf("Activity %s is not focused on screen within timeout of %d seconds", name, timeout))
}

func (am Activity) WaitForeverForActivity(name string, sampleRate time.Duration, control chan bool) error {
	for {
		select {
		case <-control:
			fmt.Println("Stop signal on waitForActivity")
			return errors.New(fmt.Sprintf("Couldn't see Activity %s before the stop signal", name))
		default:
			fmt.Printf("Waiting forever %s",sampleRate)
			stat, err := am.IsActivityFocused(name)
			if err != nil {
				return err
			}
			if stat {
				return nil
			}
			time.Sleep(sampleRate)
		}

	}
}

func (am Activity) WaitForActivityToFocusInterruptable(name string, timeout int, control chan bool) error {
	startTime := time.Now()
	for {
		select {
		case <-control:
			fmt.Println("Stop signal on waitForActivity")
			return errors.New(fmt.Sprintf("Couldn't see Activity %s before the stop signal", name))
		default:
			currentTime := time.Now()
			delta := currentTime.Sub(startTime)
			if delta.Seconds() >= float64(timeout) {
				break
			}
			stat, err := am.IsActivityFocused(name)
			if err != nil {
				return err
			}
			if stat {
				return nil
			}
		}
	}
	return errors.New(fmt.Sprintf("Activity %s is not focused on screen within timeout of %d seconds", name, timeout))
}<|endoftext|>


<commit_before>package objects

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

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"

	"github.com/mitchellh/mapstructure"
)

// Object is a structure that holds information related to a storage object.
type Object struct {
	// Bytes is the total number of bytes that comprise the object.
	Bytes int64 `json:"bytes" mapstructure:"bytes"`

	// ContentType is the content type of the object.
	ContentType string `json:"content_type" mapstructure:"content_type"`

	// Hash represents the MD5 checksum value of the object's content.
	Hash string `json:"hash" mapstructure:"hash"`

	// LastModified is the RFC3339Milli time the object was last modified, represented
	// as a string. For any given object (obj), this value may be parsed to a time.Time:
	// lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
	LastModified string `json:"last_modified" mapstructure:"last_modified"`

	// Name is the unique name for the object.
	Name string `json:"name" mapstructure:"name"`
}

// ObjectPage is a single page of objects that is returned from a call to the
// List function.
type ObjectPage struct {
	pagination.MarkerPageBase
}

// IsEmpty returns true if a ListResult contains no object names.
func (r ObjectPage) IsEmpty() (bool, error) {
	names, err := ExtractNames(r)
	if err != nil {
		return true, err
	}
	return len(names) == 0, nil
}

// LastMarker returns the last object name in a ListResult.
func (r ObjectPage) LastMarker() (string, error) {
	names, err := ExtractNames(r)
	if err != nil {
		return "", err
	}
	if len(names) == 0 {
		return "", nil
	}
	return names[len(names)-1], nil
}

// ExtractInfo is a function that takes a page of objects and returns their full information.
func ExtractInfo(page pagination.Page) ([]Object, error) {
	untyped := page.(ObjectPage).Body.([]interface{})
	results := make([]Object, len(untyped))
	for index, each := range untyped {
		object := each.(map[string]interface{})
		err := mapstructure.Decode(object, &results[index])
		if err != nil {
			return results, err
		}
	}
	return results, nil
}

// ExtractNames is a function that takes a page of objects and returns only their names.
func ExtractNames(page pagination.Page) ([]string, error) {
	casted := page.(ObjectPage)
	ct := casted.Header.Get("Content-Type")
	switch {
	case strings.HasPrefix(ct, "application/json"):
		parsed, err := ExtractInfo(page)
		if err != nil {
			return nil, err
		}

		names := make([]string, 0, len(parsed))
		for _, object := range parsed {
			names = append(names, object.Name)
		}

		return names, nil
	case strings.HasPrefix(ct, "text/plain"):
		names := make([]string, 0, 50)

		body := string(page.(ObjectPage).Body.([]uint8))
		for _, name := range strings.Split(body, "\n") {
			if len(name) > 0 {
				names = append(names, name)
			}
		}

		return names, nil
	case strings.HasPrefix(ct, "text/html"):
		return []string{}, nil
	default:
		return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
	}
}

// DownloadHeader represents the headers returned in the response from a Download request.
type DownloadHeader struct {
	AcceptRanges       string    `mapstructure:"Accept-Ranges"`
	ContentDisposition string    `mapstructure:"Content-Disposition"`
	ContentEncoding    string    `mapstructure:"Content-Encoding"`
	ContentLength      int64     `mapstructure:"Content-Length"`
	ContentType        string    `mapstructure:"Content-Type"`
	Date               time.Time `mapstructure:"-"`
	DeleteAt           int       `mapstructure:"X-Delete-At"`
	ETag               string    `mapstructure:"Etag"`
	LastModified       time.Time `mapstructure:"-"`
	ObjectManifest     string    `mapstructure:"X-Object-Manifest"`
	StaticLargeObject  bool      `mapstructure:"X-Static-Large-Object"`
	TransID            string    `mapstructure:"X-Trans-Id"`
}

// DownloadResult is a *http.Response that is returned from a call to the Download function.
type DownloadResult struct {
	gophercloud.HeaderResult
	Body io.ReadCloser
}

// Extract will return a struct of headers returned from a call to Download. To obtain
// a map of headers, call the ExtractHeader method on the DownloadResult.
func (dr DownloadResult) Extract() (DownloadHeader, error) {
	var dh DownloadHeader
	if dr.Err != nil {
		return dh, dr.Err
	}

	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
		return dh, err
	}

	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
		if err != nil {
			return dh, err
		}
		dh.Date = t
	}

	if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, dr.Header["Last-Modified"][0])
		if err != nil {
			return dh, err
		}
		dh.LastModified = t
	}

	return dh, nil
}

// ExtractContent is a function that takes a DownloadResult's io.Reader body
// and reads all available data into a slice of bytes. Please be aware that due
// the nature of io.Reader is forward-only - meaning that it can only be read
// once and not rewound. You can recreate a reader from the output of this
// function by using bytes.NewReader(downloadBytes)
func (dr DownloadResult) ExtractContent() ([]byte, error) {
	if dr.Err != nil {
		return nil, dr.Err
	}
	body, err := ioutil.ReadAll(dr.Body)
	if err != nil {
		return nil, err
	}
	dr.Body.Close()
	return body, nil
}

// GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct {
	ContentDisposition string    `mapstructure:"Content-Disposition"`
	ContentEncoding    string    `mapstructure:"Content-Encoding"`
	ContentLength      int64     `mapstructure:"Content-Length"`
	ContentType        string    `mapstructure:"Content-Type"`
	Date               time.Time `mapstructure:"-"`
	DeleteAt           int       `mapstructure:"X-Delete-At"`
	ETag               string    `mapstructure:"Etag"`
	LastModified       time.Time `mapstructure:"-"`
	ObjectManifest     string    `mapstructure:"X-Object-Manifest"`
	StaticLargeObject  bool      `mapstructure:"X-Static-Large-Object"`
	TransID            string    `mapstructure:"X-Trans-Id"`
}

// GetResult is a *http.Response that is returned from a call to the Get function.
type GetResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Get. To obtain
// a map of headers, call the ExtractHeader method on the GetResult.
func (gr GetResult) Extract() (GetHeader, error) {
	var gh GetHeader
	if gr.Err != nil {
		return gh, gr.Err
	}

	if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
		return gh, err
	}

	if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
		if err != nil {
			return gh, err
		}
		gh.Date = t
	}

	if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0])
		if err != nil {
			return gh, err
		}
		gh.LastModified = t
	}

	return gh, nil
}

// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
// and returns the custom metadata associated with the object.
func (gr GetResult) ExtractMetadata() (map[string]string, error) {
	if gr.Err != nil {
		return nil, gr.Err
	}
	metadata := make(map[string]string)
	for k, v := range gr.Header {
		if strings.HasPrefix(k, "X-Object-Meta-") {
			key := strings.TrimPrefix(k, "X-Object-Meta-")
			metadata[key] = v[0]
		}
	}
	return metadata, nil
}

// CreateHeader represents the headers returned in the response from a Create request.
type CreateHeader struct {
	ContentLength int64     `mapstructure:"Content-Length"`
	ContentType   string    `mapstructure:"Content-Type"`
	Date          time.Time `mapstructure:"-"`
	ETag          string    `mapstructure:"Etag"`
	LastModified  time.Time `mapstructure:"-"`
	TransID       string    `mapstructure:"X-Trans-Id"`
}

// CreateResult represents the result of a create operation.
type CreateResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Create. To obtain
// a map of headers, call the ExtractHeader method on the CreateResult.
func (cr CreateResult) Extract() (CreateHeader, error) {
	var ch CreateHeader
	if cr.Err != nil {
		return ch, cr.Err
	}

	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
		return ch, err
	}

	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
		if err != nil {
			return ch, err
		}
		ch.Date = t
	}

	if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
		if err != nil {
			return ch, err
		}
		ch.LastModified = t
	}

	return ch, nil
}

// UpdateHeader represents the headers returned in the response from a Update request.
type UpdateHeader struct {
	ContentLength int64     `mapstructure:"Content-Length"`
	ContentType   string    `mapstructure:"Content-Type"`
	Date          time.Time `mapstructure:"-"`
	TransID       string    `mapstructure:"X-Trans-Id"`
}

// UpdateResult represents the result of an update operation.
type UpdateResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Update. To obtain
// a map of headers, call the ExtractHeader method on the UpdateResult.
func (ur UpdateResult) Extract() (UpdateHeader, error) {
	var uh UpdateHeader
	if ur.Err != nil {
		return uh, ur.Err
	}

	if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
		return uh, err
	}

	if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
		if err != nil {
			return uh, err
		}
		uh.Date = t
	}

	return uh, nil
}

// DeleteHeader represents the headers returned in the response from a Delete request.
type DeleteHeader struct {
	ContentLength int64     `mapstructure:"Content-Length"`
	ContentType   string    `mapstructure:"Content-Type"`
	Date          time.Time `mapstructure:"-"`
	TransID       string    `mapstructure:"X-Trans-Id"`
}

// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Delete. To obtain
// a map of headers, call the ExtractHeader method on the DeleteResult.
func (dr DeleteResult) Extract() (DeleteHeader, error) {
	var dh DeleteHeader
	if dr.Err != nil {
		return dh, dr.Err
	}

	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
		return dh, err
	}

	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
		if err != nil {
			return dh, err
		}
		dh.Date = t
	}

	return dh, nil
}

// CopyHeader represents the headers returned in the response from a Copy request.
type CopyHeader struct {
	ContentLength          int64     `mapstructure:"Content-Length"`
	ContentType            string    `mapstructure:"Content-Type"`
	CopiedFrom             string    `mapstructure:"X-Copied-From"`
	CopiedFromLastModified time.Time `mapstructure:"-"`
	Date                   time.Time `mapstructure:"-"`
	ETag                   string    `mapstructure:"Etag"`
	LastModified           time.Time `mapstructure:"-"`
	TransID                string    `mapstructure:"X-Trans-Id"`
}

// CopyResult represents the result of a copy operation.
type CopyResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Copy. To obtain
// a map of headers, call the ExtractHeader method on the CopyResult.
func (cr CopyResult) Extract() (CopyHeader, error) {
	var ch CopyHeader
	if cr.Err != nil {
		return ch, cr.Err
	}

	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
		return ch, err
	}

	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
		if err != nil {
			return ch, err
		}
		ch.Date = t
	}

	if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
		if err != nil {
			return ch, err
		}
		ch.LastModified = t
	}

	if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0])
		if err != nil {
			return ch, err
		}
		ch.CopiedFromLastModified = t
	}

	return ch, nil
}
<commit_msg>convert X-Delete-At header from int to time.Time<commit_after>package objects

import (
	"fmt"
	"io"
	"io/ioutil"
	"strconv"
	"strings"
	"time"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"

	"github.com/mitchellh/mapstructure"
)

// Object is a structure that holds information related to a storage object.
type Object struct {
	// Bytes is the total number of bytes that comprise the object.
	Bytes int64 `json:"bytes" mapstructure:"bytes"`

	// ContentType is the content type of the object.
	ContentType string `json:"content_type" mapstructure:"content_type"`

	// Hash represents the MD5 checksum value of the object's content.
	Hash string `json:"hash" mapstructure:"hash"`

	// LastModified is the RFC3339Milli time the object was last modified, represented
	// as a string. For any given object (obj), this value may be parsed to a time.Time:
	// lastModified, err := time.Parse(gophercloud.RFC3339Milli, obj.LastModified)
	LastModified string `json:"last_modified" mapstructure:"last_modified"`

	// Name is the unique name for the object.
	Name string `json:"name" mapstructure:"name"`
}

// ObjectPage is a single page of objects that is returned from a call to the
// List function.
type ObjectPage struct {
	pagination.MarkerPageBase
}

// IsEmpty returns true if a ListResult contains no object names.
func (r ObjectPage) IsEmpty() (bool, error) {
	names, err := ExtractNames(r)
	if err != nil {
		return true, err
	}
	return len(names) == 0, nil
}

// LastMarker returns the last object name in a ListResult.
func (r ObjectPage) LastMarker() (string, error) {
	names, err := ExtractNames(r)
	if err != nil {
		return "", err
	}
	if len(names) == 0 {
		return "", nil
	}
	return names[len(names)-1], nil
}

// ExtractInfo is a function that takes a page of objects and returns their full information.
func ExtractInfo(page pagination.Page) ([]Object, error) {
	untyped := page.(ObjectPage).Body.([]interface{})
	results := make([]Object, len(untyped))
	for index, each := range untyped {
		object := each.(map[string]interface{})
		err := mapstructure.Decode(object, &results[index])
		if err != nil {
			return results, err
		}
	}
	return results, nil
}

// ExtractNames is a function that takes a page of objects and returns only their names.
func ExtractNames(page pagination.Page) ([]string, error) {
	casted := page.(ObjectPage)
	ct := casted.Header.Get("Content-Type")
	switch {
	case strings.HasPrefix(ct, "application/json"):
		parsed, err := ExtractInfo(page)
		if err != nil {
			return nil, err
		}

		names := make([]string, 0, len(parsed))
		for _, object := range parsed {
			names = append(names, object.Name)
		}

		return names, nil
	case strings.HasPrefix(ct, "text/plain"):
		names := make([]string, 0, 50)

		body := string(page.(ObjectPage).Body.([]uint8))
		for _, name := range strings.Split(body, "\n") {
			if len(name) > 0 {
				names = append(names, name)
			}
		}

		return names, nil
	case strings.HasPrefix(ct, "text/html"):
		return []string{}, nil
	default:
		return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
	}
}

// DownloadHeader represents the headers returned in the response from a Download request.
type DownloadHeader struct {
	AcceptRanges       string    `mapstructure:"Accept-Ranges"`
	ContentDisposition string    `mapstructure:"Content-Disposition"`
	ContentEncoding    string    `mapstructure:"Content-Encoding"`
	ContentLength      int64     `mapstructure:"Content-Length"`
	ContentType        string    `mapstructure:"Content-Type"`
	Date               time.Time `mapstructure:"-"`
	DeleteAt           time.Time `mapstructure:"-"`
	ETag               string    `mapstructure:"Etag"`
	LastModified       time.Time `mapstructure:"-"`
	ObjectManifest     string    `mapstructure:"X-Object-Manifest"`
	StaticLargeObject  bool      `mapstructure:"X-Static-Large-Object"`
	TransID            string    `mapstructure:"X-Trans-Id"`
}

// DownloadResult is a *http.Response that is returned from a call to the Download function.
type DownloadResult struct {
	gophercloud.HeaderResult
	Body io.ReadCloser
}

// Extract will return a struct of headers returned from a call to Download. To obtain
// a map of headers, call the ExtractHeader method on the DownloadResult.
func (dr DownloadResult) Extract() (DownloadHeader, error) {
	var dh DownloadHeader
	if dr.Err != nil {
		return dh, dr.Err
	}

	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
		return dh, err
	}

	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, date[0])
		if err != nil {
			return dh, err
		}
		dh.Date = t
	}

	if date, ok := dr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, date[0])
		if err != nil {
			return dh, err
		}
		dh.LastModified = t
	}

	if date, ok := dr.Header["X-Delete-At"]; ok && len(date) > 0 {
		unix, err := strconv.ParseInt(date[0], 10, 64)
		if err != nil {
			return dh, err
		}
		dh.DeleteAt = time.Unix(unix, 0)
	}

	return dh, nil
}

// ExtractContent is a function that takes a DownloadResult's io.Reader body
// and reads all available data into a slice of bytes. Please be aware that due
// the nature of io.Reader is forward-only - meaning that it can only be read
// once and not rewound. You can recreate a reader from the output of this
// function by using bytes.NewReader(downloadBytes)
func (dr DownloadResult) ExtractContent() ([]byte, error) {
	if dr.Err != nil {
		return nil, dr.Err
	}
	body, err := ioutil.ReadAll(dr.Body)
	if err != nil {
		return nil, err
	}
	dr.Body.Close()
	return body, nil
}

// GetHeader represents the headers returned in the response from a Get request.
type GetHeader struct {
	ContentDisposition string    `mapstructure:"Content-Disposition"`
	ContentEncoding    string    `mapstructure:"Content-Encoding"`
	ContentLength      int64     `mapstructure:"Content-Length"`
	ContentType        string    `mapstructure:"Content-Type"`
	Date               time.Time `mapstructure:"-"`
	DeleteAt           time.Time `mapstructure:"-"`
	ETag               string    `mapstructure:"Etag"`
	LastModified       time.Time `mapstructure:"-"`
	ObjectManifest     string    `mapstructure:"X-Object-Manifest"`
	StaticLargeObject  bool      `mapstructure:"X-Static-Large-Object"`
	TransID            string    `mapstructure:"X-Trans-Id"`
}

// GetResult is a *http.Response that is returned from a call to the Get function.
type GetResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Get. To obtain
// a map of headers, call the ExtractHeader method on the GetResult.
func (gr GetResult) Extract() (GetHeader, error) {
	var gh GetHeader
	if gr.Err != nil {
		return gh, gr.Err
	}

	if err := gophercloud.DecodeHeader(gr.Header, &gh); err != nil {
		return gh, err
	}

	if date, ok := gr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, gr.Header["Date"][0])
		if err != nil {
			return gh, err
		}
		gh.Date = t
	}

	if date, ok := gr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, gr.Header["Last-Modified"][0])
		if err != nil {
			return gh, err
		}
		gh.LastModified = t
	}

	if date, ok := gr.Header["X-Delete-At"]; ok && len(date) > 0 {
		unix, err := strconv.ParseInt(date[0], 10, 64)
		if err != nil {
			return gh, err
		}
		gh.DeleteAt = time.Unix(unix, 0)
	}

	return gh, nil
}

// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
// and returns the custom metadata associated with the object.
func (gr GetResult) ExtractMetadata() (map[string]string, error) {
	if gr.Err != nil {
		return nil, gr.Err
	}
	metadata := make(map[string]string)
	for k, v := range gr.Header {
		if strings.HasPrefix(k, "X-Object-Meta-") {
			key := strings.TrimPrefix(k, "X-Object-Meta-")
			metadata[key] = v[0]
		}
	}
	return metadata, nil
}

// CreateHeader represents the headers returned in the response from a Create request.
type CreateHeader struct {
	ContentLength int64     `mapstructure:"Content-Length"`
	ContentType   string    `mapstructure:"Content-Type"`
	Date          time.Time `mapstructure:"-"`
	ETag          string    `mapstructure:"Etag"`
	LastModified  time.Time `mapstructure:"-"`
	TransID       string    `mapstructure:"X-Trans-Id"`
}

// CreateResult represents the result of a create operation.
type CreateResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Create. To obtain
// a map of headers, call the ExtractHeader method on the CreateResult.
func (cr CreateResult) Extract() (CreateHeader, error) {
	var ch CreateHeader
	if cr.Err != nil {
		return ch, cr.Err
	}

	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
		return ch, err
	}

	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
		if err != nil {
			return ch, err
		}
		ch.Date = t
	}

	if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
		if err != nil {
			return ch, err
		}
		ch.LastModified = t
	}

	return ch, nil
}

// UpdateHeader represents the headers returned in the response from a Update request.
type UpdateHeader struct {
	ContentLength int64     `mapstructure:"Content-Length"`
	ContentType   string    `mapstructure:"Content-Type"`
	Date          time.Time `mapstructure:"-"`
	TransID       string    `mapstructure:"X-Trans-Id"`
}

// UpdateResult represents the result of an update operation.
type UpdateResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Update. To obtain
// a map of headers, call the ExtractHeader method on the UpdateResult.
func (ur UpdateResult) Extract() (UpdateHeader, error) {
	var uh UpdateHeader
	if ur.Err != nil {
		return uh, ur.Err
	}

	if err := gophercloud.DecodeHeader(ur.Header, &uh); err != nil {
		return uh, err
	}

	if date, ok := ur.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, ur.Header["Date"][0])
		if err != nil {
			return uh, err
		}
		uh.Date = t
	}

	return uh, nil
}

// DeleteHeader represents the headers returned in the response from a Delete request.
type DeleteHeader struct {
	ContentLength int64     `mapstructure:"Content-Length"`
	ContentType   string    `mapstructure:"Content-Type"`
	Date          time.Time `mapstructure:"-"`
	TransID       string    `mapstructure:"X-Trans-Id"`
}

// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Delete. To obtain
// a map of headers, call the ExtractHeader method on the DeleteResult.
func (dr DeleteResult) Extract() (DeleteHeader, error) {
	var dh DeleteHeader
	if dr.Err != nil {
		return dh, dr.Err
	}

	if err := gophercloud.DecodeHeader(dr.Header, &dh); err != nil {
		return dh, err
	}

	if date, ok := dr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, dr.Header["Date"][0])
		if err != nil {
			return dh, err
		}
		dh.Date = t
	}

	return dh, nil
}

// CopyHeader represents the headers returned in the response from a Copy request.
type CopyHeader struct {
	ContentLength          int64     `mapstructure:"Content-Length"`
	ContentType            string    `mapstructure:"Content-Type"`
	CopiedFrom             string    `mapstructure:"X-Copied-From"`
	CopiedFromLastModified time.Time `mapstructure:"-"`
	Date                   time.Time `mapstructure:"-"`
	ETag                   string    `mapstructure:"Etag"`
	LastModified           time.Time `mapstructure:"-"`
	TransID                string    `mapstructure:"X-Trans-Id"`
}

// CopyResult represents the result of a copy operation.
type CopyResult struct {
	gophercloud.HeaderResult
}

// Extract will return a struct of headers returned from a call to Copy. To obtain
// a map of headers, call the ExtractHeader method on the CopyResult.
func (cr CopyResult) Extract() (CopyHeader, error) {
	var ch CopyHeader
	if cr.Err != nil {
		return ch, cr.Err
	}

	if err := gophercloud.DecodeHeader(cr.Header, &ch); err != nil {
		return ch, err
	}

	if date, ok := cr.Header["Date"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Date"][0])
		if err != nil {
			return ch, err
		}
		ch.Date = t
	}

	if date, ok := cr.Header["Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["Last-Modified"][0])
		if err != nil {
			return ch, err
		}
		ch.LastModified = t
	}

	if date, ok := cr.Header["X-Copied-From-Last-Modified"]; ok && len(date) > 0 {
		t, err := time.Parse(time.RFC1123, cr.Header["X-Copied-From-Last-Modified"][0])
		if err != nil {
			return ch, err
		}
		ch.CopiedFromLastModified = t
	}

	return ch, nil
}
<|endoftext|>


<commit_before>package stacks

import (
	"errors"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
	ToStackCreateMap() (map[string]interface{}, error)
}

// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
	DisableRollback *bool
	Environment     string
	Files           map[string]interface{}
	Name            string
	Parameters      map[string]string
	Template        string
	TemplateURL     string
	Timeout         int
}

// ToStackCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return s, errors.New("Required field 'Name' not provided.")
	}
	s["stack_name"] = opts.Name

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.DisableRollback != nil {
		s["disable_rollback"] = &opts.DisableRollback
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}
	if opts.Files != nil {
		s["files"] = opts.Files
	}
	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return s, nil
}

// Create accepts a CreateOpts struct and creates a new stack using the values
// provided.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToStackCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		Results:     &res.Body,
		OkCodes:     []int{201},
	})
	return res
}

// AdoptOptsBuilder is the interface options structs have to satisfy in order
// to be used in the Adopt function in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type AdoptOptsBuilder interface {
	ToStackAdoptMap() (map[string]interface{}, error)
}

// AdoptOpts is the common options struct used in this package's Adopt
// operation.
type AdoptOpts struct {
	AdoptStackData  string
	DisableRollback *bool
	Environment     string
	Files           map[string]interface{}
	Name            string
	Parameters      map[string]string
	Template        string
	TemplateURL     string
	Timeout         int
}

// ToStackAdoptMap casts a CreateOpts struct to a map.
func (opts AdoptOpts) ToStackAdoptMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return s, errors.New("Required field 'Name' not provided.")
	}
	s["stack_name"] = opts.Name

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.AdoptStackData == "" {
		return s, errors.New("Required field 'AdoptStackData' not provided.")
	}
	s["adopt_stack_data"] = opts.AdoptStackData

	if opts.DisableRollback != nil {
		s["disable_rollback"] = &opts.DisableRollback
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}
	if opts.Files != nil {
		s["files"] = opts.Files
	}
	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return map[string]interface{}{"stack": s}, nil
}

// Adopt accepts an AdoptOpts struct and creates a new stack using the resources
// from another stack.
func Adopt(c *gophercloud.ServiceClient, opts AdoptOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToStackAdoptMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("POST", adoptURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		Results:     &res.Body,
		OkCodes:     []int{201},
	})
	return res
}

// SortDir is a type for specifying in which direction to sort a list of stacks.
type SortDir string

// SortKey is a type for specifying by which key to sort a list of stacks.
type SortKey string

var (
	// SortAsc is used to sort a list of stacks in ascending order.
	SortAsc SortDir = "asc"
	// SortDesc is used to sort a list of stacks in descending order.
	SortDesc SortDir = "desc"
	// SortName is used to sort a list of stacks by name.
	SortName SortKey = "name"
	// SortStatus is used to sort a list of stacks by status.
	SortStatus SortKey = "status"
	// SortCreatedAt is used to sort a list of stacks by date created.
	SortCreatedAt SortKey = "created_at"
	// SortUpdatedAt is used to sort a list of stacks by date updated.
	SortUpdatedAt SortKey = "updated_at"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToStackListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the network attributes you want to see returned. SortKey allows you to sort
// by a particular network attribute. SortDir sets the direction, and is either
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
	Status  string  `q:"status"`
	Name    string  `q:"name"`
	Marker  string  `q:"marker"`
	Limit   int     `q:"limit"`
	SortKey SortKey `q:"sort_keys"`
	SortDir SortDir `q:"sort_dir"`
}

// ToStackListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToStackListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List returns a Pager which allows you to iterate over a collection of
// stacks. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)
	if opts != nil {
		query, err := opts.ToStackListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPage := func(r pagination.PageResult) pagination.Page {
		return StackPage{pagination.SinglePageBase(r)}
	}
	return pagination.NewPager(c, url, createPage)
}

// Get retreives a stack based on the stack name and stack ID.
func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult {
	var res GetResult

	// Send request to API
	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}

// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the Update operation in this package.
type UpdateOptsBuilder interface {
	ToStackUpdateMap() (map[string]interface{}, error)
}

// UpdateOpts contains the common options struct used in this package's Update
// operation.
type UpdateOpts struct {
	Environment string
	Files       map[string]interface{}
	Parameters  map[string]string
	Template    string
	TemplateURL string
	Timeout     int
}

// ToStackUpdateMap casts a CreateOpts struct to a map.
func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}

	if opts.Files != nil {
		s["files"] = opts.Files
	}

	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return s, nil
}

// Update accepts an UpdateOpts struct and updates an existing stack using the values
// provided.
func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult

	reqBody, err := opts.ToStackUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("PUT", updateURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		OkCodes:     []int{202},
	})
	return res
}

// Delete deletes a stack based on the stack name and stack ID.
func Delete(c *gophercloud.ServiceClient, stackName, stackID string) DeleteResult {
	var res DeleteResult

	// Send request to API
	_, res.Err = perigee.Request("DELETE", deleteURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		OkCodes:     []int{204},
	})
	return res
}

// PreviewOptsBuilder is the interface options structs have to satisfy in order
// to be used in the Preview operation in this package.
type PreviewOptsBuilder interface {
	ToStackPreviewMap() (map[string]interface{}, error)
}

// PreviewOpts contains the common options struct used in this package's Preview
// operation.
type PreviewOpts struct {
	DisableRollback *bool
	Environment     string
	Files           map[string]interface{}
	Name            string
	Parameters      map[string]string
	Template        string
	TemplateURL     string
	Timeout         int
}

// ToStackPreviewMap casts a PreviewOpts struct to a map.
func (opts PreviewOpts) ToStackPreviewMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return s, errors.New("Required field 'Name' not provided.")
	}
	s["stack_name"] = opts.Name

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.DisableRollback != nil {
		s["disable_rollback"] = &opts.DisableRollback
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}
	if opts.Files != nil {
		s["files"] = opts.Files
	}
	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return s, nil
}

// Preview accepts a PreviewOptsBuilder interface and creates a preview of a stack using the values
// provided.
func Preview(c *gophercloud.ServiceClient, opts PreviewOptsBuilder) PreviewResult {
	var res PreviewResult

	reqBody, err := opts.ToStackPreviewMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("POST", previewURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}

// Abandon deletes the stack with the provided stackName and stackID, but leaves its
// resources intact, and returns data describing the stack and its resources.
func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) AbandonResult {
	var res AbandonResult

	// Send request to API
	_, res.Err = perigee.Request("POST", abandonURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}
<commit_msg>comments for CreateOpts fields<commit_after>package stacks

import (
	"errors"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/pagination"
)

// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type CreateOptsBuilder interface {
	ToStackCreateMap() (map[string]interface{}, error)
}

// CreateOpts is the common options struct used in this package's Create
// operation.
type CreateOpts struct {
	// (REQUIRED) The name of the stack. It must start with an alphabetic character.
	Name string
	// (REQUIRED) The timeout for stack creation in minutes.
	Timeout int
	// (OPTIONAL; REQUIRED IF Template IS EMPTY) The URL of the template to instantiate.
	// This value is ignored if Template is supplied inline.
	TemplateURL string
	// (OPTIONAL; REQUIRED IF TemplateURL IS EMPTY) A template to instantiate. The value
	// is a stringified version of the JSON/YAML template. Since the template will likely
	// be located in a file, one way to set this variable is by using ioutil.ReadFile:
	// import "io/ioutil"
	// var opts stacks.CreateOpts
	// b, err := ioutil.ReadFile("path/to/you/template/file.json")
	// if err != nil {
	//   // handle error...
	// }
	// opts.Template = string(b)
	Template string
	// (OPTIONAL) Enables or disables deletion of all stack resources when a stack
	// creation fails. Default is true, meaning all resources are not deleted when
	// stack creation fails.
	DisableRollback *bool
	// (OPTIONAL) A stringified JSON environment for the stack.
	Environment string
	// (OPTIONAL) A map that maps file names to file contents. It can also be used
	// to pass provider template contents. Example:
	// Files: `{"myfile": "#!/bin/bash\necho 'Hello world' > /root/testfile.txt"}`
	Files map[string]interface{}
	// (OPTIONAL) User-defined parameters to pass to the template.
	Parameters map[string]string
}

// ToStackCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToStackCreateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return s, errors.New("Required field 'Name' not provided.")
	}
	s["stack_name"] = opts.Name

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.DisableRollback != nil {
		s["disable_rollback"] = &opts.DisableRollback
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}
	if opts.Files != nil {
		s["files"] = opts.Files
	}
	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return s, nil
}

// Create accepts a CreateOpts struct and creates a new stack using the values
// provided.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToStackCreateMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("POST", createURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		Results:     &res.Body,
		OkCodes:     []int{201},
	})
	return res
}

// AdoptOptsBuilder is the interface options structs have to satisfy in order
// to be used in the Adopt function in this package. Since many
// extensions decorate or modify the common logic, it is useful for them to
// satisfy a basic interface in order for them to be used.
type AdoptOptsBuilder interface {
	ToStackAdoptMap() (map[string]interface{}, error)
}

// AdoptOpts is the common options struct used in this package's Adopt
// operation.
type AdoptOpts struct {
	AdoptStackData  string
	DisableRollback *bool
	Environment     string
	Files           map[string]interface{}
	Name            string
	Parameters      map[string]string
	Template        string
	TemplateURL     string
	Timeout         int
}

// ToStackAdoptMap casts a CreateOpts struct to a map.
func (opts AdoptOpts) ToStackAdoptMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return s, errors.New("Required field 'Name' not provided.")
	}
	s["stack_name"] = opts.Name

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.AdoptStackData == "" {
		return s, errors.New("Required field 'AdoptStackData' not provided.")
	}
	s["adopt_stack_data"] = opts.AdoptStackData

	if opts.DisableRollback != nil {
		s["disable_rollback"] = &opts.DisableRollback
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}
	if opts.Files != nil {
		s["files"] = opts.Files
	}
	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return map[string]interface{}{"stack": s}, nil
}

// Adopt accepts an AdoptOpts struct and creates a new stack using the resources
// from another stack.
func Adopt(c *gophercloud.ServiceClient, opts AdoptOptsBuilder) CreateResult {
	var res CreateResult

	reqBody, err := opts.ToStackAdoptMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("POST", adoptURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		Results:     &res.Body,
		OkCodes:     []int{201},
	})
	return res
}

// SortDir is a type for specifying in which direction to sort a list of stacks.
type SortDir string

// SortKey is a type for specifying by which key to sort a list of stacks.
type SortKey string

var (
	// SortAsc is used to sort a list of stacks in ascending order.
	SortAsc SortDir = "asc"
	// SortDesc is used to sort a list of stacks in descending order.
	SortDesc SortDir = "desc"
	// SortName is used to sort a list of stacks by name.
	SortName SortKey = "name"
	// SortStatus is used to sort a list of stacks by status.
	SortStatus SortKey = "status"
	// SortCreatedAt is used to sort a list of stacks by date created.
	SortCreatedAt SortKey = "created_at"
	// SortUpdatedAt is used to sort a list of stacks by date updated.
	SortUpdatedAt SortKey = "updated_at"
)

// ListOptsBuilder allows extensions to add additional parameters to the
// List request.
type ListOptsBuilder interface {
	ToStackListQuery() (string, error)
}

// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the network attributes you want to see returned. SortKey allows you to sort
// by a particular network attribute. SortDir sets the direction, and is either
// `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
	Status  string  `q:"status"`
	Name    string  `q:"name"`
	Marker  string  `q:"marker"`
	Limit   int     `q:"limit"`
	SortKey SortKey `q:"sort_keys"`
	SortDir SortDir `q:"sort_dir"`
}

// ToStackListQuery formats a ListOpts into a query string.
func (opts ListOpts) ToStackListQuery() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// List returns a Pager which allows you to iterate over a collection of
// stacks. It accepts a ListOpts struct, which allows you to filter and sort
// the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
	url := listURL(c)
	if opts != nil {
		query, err := opts.ToStackListQuery()
		if err != nil {
			return pagination.Pager{Err: err}
		}
		url += query
	}

	createPage := func(r pagination.PageResult) pagination.Page {
		return StackPage{pagination.SinglePageBase(r)}
	}
	return pagination.NewPager(c, url, createPage)
}

// Get retreives a stack based on the stack name and stack ID.
func Get(c *gophercloud.ServiceClient, stackName, stackID string) GetResult {
	var res GetResult

	// Send request to API
	_, res.Err = perigee.Request("GET", getURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}

// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the Update operation in this package.
type UpdateOptsBuilder interface {
	ToStackUpdateMap() (map[string]interface{}, error)
}

// UpdateOpts contains the common options struct used in this package's Update
// operation.
type UpdateOpts struct {
	Environment string
	Files       map[string]interface{}
	Parameters  map[string]string
	Template    string
	TemplateURL string
	Timeout     int
}

// ToStackUpdateMap casts a CreateOpts struct to a map.
func (opts UpdateOpts) ToStackUpdateMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}

	if opts.Files != nil {
		s["files"] = opts.Files
	}

	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return s, nil
}

// Update accepts an UpdateOpts struct and updates an existing stack using the values
// provided.
func Update(c *gophercloud.ServiceClient, stackName, stackID string, opts UpdateOptsBuilder) UpdateResult {
	var res UpdateResult

	reqBody, err := opts.ToStackUpdateMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("PUT", updateURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		OkCodes:     []int{202},
	})
	return res
}

// Delete deletes a stack based on the stack name and stack ID.
func Delete(c *gophercloud.ServiceClient, stackName, stackID string) DeleteResult {
	var res DeleteResult

	// Send request to API
	_, res.Err = perigee.Request("DELETE", deleteURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		OkCodes:     []int{204},
	})
	return res
}

// PreviewOptsBuilder is the interface options structs have to satisfy in order
// to be used in the Preview operation in this package.
type PreviewOptsBuilder interface {
	ToStackPreviewMap() (map[string]interface{}, error)
}

// PreviewOpts contains the common options struct used in this package's Preview
// operation.
type PreviewOpts struct {
	DisableRollback *bool
	Environment     string
	Files           map[string]interface{}
	Name            string
	Parameters      map[string]string
	Template        string
	TemplateURL     string
	Timeout         int
}

// ToStackPreviewMap casts a PreviewOpts struct to a map.
func (opts PreviewOpts) ToStackPreviewMap() (map[string]interface{}, error) {
	s := make(map[string]interface{})

	if opts.Name == "" {
		return s, errors.New("Required field 'Name' not provided.")
	}
	s["stack_name"] = opts.Name

	if opts.Template != "" {
		s["template"] = opts.Template
	} else if opts.TemplateURL != "" {
		s["template_url"] = opts.TemplateURL
	} else {
		return s, errors.New("Either Template or TemplateURL must be provided.")
	}

	if opts.DisableRollback != nil {
		s["disable_rollback"] = &opts.DisableRollback
	}

	if opts.Environment != "" {
		s["environment"] = opts.Environment
	}
	if opts.Files != nil {
		s["files"] = opts.Files
	}
	if opts.Parameters != nil {
		s["parameters"] = opts.Parameters
	}

	if opts.Timeout != 0 {
		s["timeout_mins"] = opts.Timeout
	}

	return s, nil
}

// Preview accepts a PreviewOptsBuilder interface and creates a preview of a stack using the values
// provided.
func Preview(c *gophercloud.ServiceClient, opts PreviewOptsBuilder) PreviewResult {
	var res PreviewResult

	reqBody, err := opts.ToStackPreviewMap()
	if err != nil {
		res.Err = err
		return res
	}

	// Send request to API
	_, res.Err = perigee.Request("POST", previewURL(c), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		ReqBody:     &reqBody,
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}

// Abandon deletes the stack with the provided stackName and stackID, but leaves its
// resources intact, and returns data describing the stack and its resources.
func Abandon(c *gophercloud.ServiceClient, stackName, stackID string) AbandonResult {
	var res AbandonResult

	// Send request to API
	_, res.Err = perigee.Request("POST", abandonURL(c, stackName, stackID), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		Results:     &res.Body,
		OkCodes:     []int{200},
	})
	return res
}
<|endoftext|>


<commit_before>package mbus

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/nats-io/nats.go"

	"crypto/x509"
	"time"

	"crypto/tls"
	"regexp"

	boshhandler "github.com/cloudfoundry/bosh-agent/handler"
	boshplatform "github.com/cloudfoundry/bosh-agent/platform"
	boshsettings "github.com/cloudfoundry/bosh-agent/settings"
	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
)

const (
	responseMaxLength        = 1024 * 1024
	natsHandlerLogTag        = "NATS Handler"
	natsMinRetryWait         = 2
	natsConnectionMaxRetries = 10
	//natsMaxReconnectWait should match the setting we have in BOSH for https://github.com/cloudfoundry/bosh/blob/main/src/bosh-director/lib/bosh/director/agent_client.rb#L44. For now defaulting to 45 seconds. We could propagate this value in the settings.json with the other mbus settings.
	natsMaxReconnectWait = 45 * time.Second
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate

type Handler interface {
	Run(boshhandler.Func) error
	Start(boshhandler.Func) error
	RegisterAdditionalFunc(boshhandler.Func)
	Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error
	Stop()
}

type NatsConnector func(url string, options ...nats.Option) (NatsConnection, error)

//counterfeiter:generate . NatsConnection

type NatsConnection interface {
	Close()
	Publish(subj string, data []byte) error
	Subscribe(subj string, cb nats.MsgHandler) (*nats.Subscription, error)
}

type natsHandler struct {
	settingsService boshsettings.Service
	connector       NatsConnector
	connection      NatsConnection
	platform        boshplatform.Platform

	handlerFuncs     []boshhandler.Func
	handlerFuncsLock sync.Mutex

	logger      boshlog.Logger
	auditLogger boshplatform.AuditLogger
	logTag      string
}

type ConnectionInfo struct {
	Addr      string
	IP        string
	Dial      func(network, address string) (net.Conn, error)
	TLSConfig *tls.Config
}

type ConnectionTLSInfo struct {
	CertPool              *x509.CertPool
	ClientCert            *tls.Certificate
	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
}

func NewNatsHandler(
	settingsService boshsettings.Service,
	client NatsConnector,
	logger boshlog.Logger,
	platform boshplatform.Platform,
) Handler {
	return &natsHandler{
		settingsService: settingsService,
		connector:       client,
		platform:        platform,
		logger:          logger,
		logTag:          natsHandlerLogTag,
		auditLogger:     platform.GetAuditLogger(),
	}
}
func (h *natsHandler) arpClean() {
	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		h.logger.Error(h.logTag, fmt.Sprintf("%v", bosherr.WrapError(err, "Getting connection info")))
	}
	err = h.platform.DeleteARPEntryWithIP(connectionInfo.IP)
	if err != nil {
		h.logger.Error(h.logTag, "Cleaning ip-mac address cache for: %s. Error: %v", connectionInfo.IP, err)
	}
	h.logger.Debug(h.logTag, "Cleaned ip-mac address cache for: %s.", connectionInfo.IP)
}

func (h *natsHandler) Run(handlerFunc boshhandler.Func) error {
	err := h.Start(handlerFunc)
	defer h.Stop()

	if err != nil {
		return bosherr.WrapError(err, "Starting nats handler")
	}

	h.runUntilInterrupted()
	return nil
}
func (h *natsHandler) Start(handlerFunc boshhandler.Func) error {
	h.RegisterAdditionalFunc(handlerFunc)

	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		return bosherr.WrapError(err, "Getting connection info")
	}
	h.logger.Info(h.logTag, "Attempting to connect to NATS")
	if net.ParseIP(connectionInfo.IP) != nil {
		h.arpClean()
	}
	var natsOptions = []nats.Option{
		nats.DisconnectErrHandler(func(c *nats.Conn, err error) {
			h.logger.Debug(natsHandlerLogTag, "Nats disconnected with Error: %v", err.Error())
			h.logger.Debug(natsHandlerLogTag, "Attempting to reconnect: %v", c.IsReconnecting())

			for c.IsReconnecting() {
				h.arpClean()
				h.logger.Debug(natsHandlerLogTag, fmt.Sprintf("Waiting to reconnect to nats.. Connected: %v", c.IsConnected()))
				time.Sleep(time.Second)
			}
		}),
		nats.ReconnectHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Reconnected to %v", c.ConnectedAddr())
		}),
		nats.ClosedHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Connection Closed with: %v", c.LastError().Error())
		}),
		nats.ErrorHandler(func(c *nats.Conn, s *nats.Subscription, err error) {
			h.logger.Debug(natsHandlerLogTag, err.Error())
		}),
		nats.CustomReconnectDelay(func(attempts int) time.Duration {
			exponentialReconnectWait := time.Duration(math.Pow(natsMinRetryWait, float64(attempts))) * time.Second
			if natsMaxReconnectWait > exponentialReconnectWait {
				h.logger.Debug(natsHandlerLogTag, fmt.Sprintf("Increased reconnect to: %v", exponentialReconnectWait))
				return exponentialReconnectWait
			}
			h.logger.Debug(natsHandlerLogTag, fmt.Sprintf("Increased reconnect to: %v", natsMaxReconnectWait))
			return natsMaxReconnectWait
		}),
		nats.MaxReconnects(natsConnectionMaxRetries),
		nats.Secure(connectionInfo.TLSConfig),
	}

	connection, err := h.connector(connectionInfo.Addr, natsOptions...)
	if err != nil {
		return bosherr.WrapError(err, "Connecting to NATS")
	}
	h.connection = connection

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("agent.%s", settings.AgentID)

	h.logger.Info(h.logTag, "Subscribing to %s", subject)

	_, err = h.connection.Subscribe(subject, func(natsMsg *nats.Msg) {
		// Do not lock handler funcs around possible network calls!
		h.handlerFuncsLock.Lock()
		handlerFuncs := h.handlerFuncs
		h.handlerFuncsLock.Unlock()

		for _, handlerFunc := range handlerFuncs {
			h.handleNatsMsg(natsMsg, handlerFunc)
		}
	})
	if err != nil {
		return bosherr.WrapErrorf(err, "Subscribing to %s", subject)
	}

	return nil
}

func (h *natsHandler) RegisterAdditionalFunc(handlerFunc boshhandler.Func) {
	// Currently not locking since RegisterAdditionalFunc
	// is not a primary way of adding handlerFunc.
	h.handlerFuncsLock.Lock()
	h.handlerFuncs = append(h.handlerFuncs, handlerFunc)
	h.handlerFuncsLock.Unlock()
}

func (h *natsHandler) Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error {
	bytes, err := json.Marshal(message)
	if err != nil {
		return bosherr.WrapErrorf(err, "Marshalling message (target=%s, topic=%s): %#v", target, topic, message)
	}

	h.logger.Info(h.logTag, "Sending %s message '%s'", target, topic)
	h.logger.DebugWithDetails(h.logTag, "Message Payload", string(bytes))

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("%s.agent.%s.%s", target, topic, settings.AgentID)
	if h.connection != nil {
		return h.connection.Publish(subject, bytes)
	}
	return nil
}

func (h *natsHandler) Stop() {
	if h.connection != nil {
		h.connection.Close()
	}
}

func (h *natsHandler) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	natsBoshInternalsRegexp := regexp.MustCompile(`^[a-zA-Z0-9*\-]*.nats.bosh-internal$`)
	for _, chain := range verifiedChains {
		if len(chain) == 0 {
			continue
		}
		commonName := chain[0].Subject.CommonName
		if natsBoshInternalsRegexp.MatchString(commonName) {
			return nil
		}
	}
	return errors.New("server Certificate CommonName does not match *.nats.bosh-internal")
}

func (h *natsHandler) handleNatsMsg(natsMsg *nats.Msg, handlerFunc boshhandler.Func) {
	respBytes, req, err := boshhandler.PerformHandlerWithJSON(
		natsMsg.Data,
		handlerFunc,
		responseMaxLength,
		h.logger,
	)

	if err != nil {
		h.logger.Error(h.logTag, "Running handler: %s", err)
		h.generateCEFLog(natsMsg, 7, err.Error())
		return
	}

	if len(respBytes) > 0 {
		err = h.connection.Publish(req.ReplyTo, respBytes)
		if err != nil {
			h.generateCEFLog(natsMsg, 7, err.Error())
			h.logger.Error(h.logTag, "Publishing to the client: %s", err.Error())
			return
		}
	}

	h.generateCEFLog(natsMsg, 1, "")
}

func (h *natsHandler) runUntilInterrupted() {
	defer h.connection.Close()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)

	keepRunning := true
	for keepRunning {
		<-c
		keepRunning = false
	}
}

func (h *natsHandler) getConnectionInfo() (*ConnectionInfo, error) {
	settings := h.settingsService.GetSettings()

	connInfo := new(ConnectionInfo)
	connInfo.Addr = settings.GetMbusURL()
	natsURL, err := url.Parse(connInfo.Addr)
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing Nats URL")
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	connInfo.IP = hostSplit[0]

	connInfo.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12}

	caCert := settings.GetMbusCerts().CA
	if caCert != "" {
		connInfo.TLSConfig.RootCAs = x509.NewCertPool()
		if ok := connInfo.TLSConfig.RootCAs.AppendCertsFromPEM([]byte(caCert)); !ok {
			return nil, bosherr.Error("Failed to load Mbus CA cert")
		}
	}

	connInfo.TLSConfig.VerifyPeerCertificate = h.VerifyPeerCertificate

	clientCertificate, err := tls.X509KeyPair([]byte(settings.GetMbusCerts().Certificate), []byte(settings.GetMbusCerts().PrivateKey))
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing certificate and private key")
	}
	connInfo.TLSConfig.Certificates = []tls.Certificate{clientCertificate}

	return connInfo, nil
}

func (h *natsHandler) generateCEFLog(natsMsg *nats.Msg, severity int, statusReason string) {
	cef := boshhandler.NewCommonEventFormat()

	settings := h.settingsService.GetSettings()

	natsURL, err := url.Parse(settings.GetMbusURL())
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	ip := hostSplit[0]
	payload := struct {
		Method  string `json:"method"`
		ReplyTo string `json:"reply_to"`
	}{}
	err = json.Unmarshal(natsMsg.Data, &payload)
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
	}
	cefString, err := cef.ProduceNATSRequestEventLog(ip, hostSplit[1], payload.ReplyTo, payload.Method, severity, natsMsg.Subject, statusReason)

	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	if severity == 7 {
		h.auditLogger.Err(cefString)
		return
	}

	h.auditLogger.Debug(cefString)
}
<commit_msg>Add finetuning for nats client<commit_after>package mbus

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net"
	"net/url"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/nats-io/nats.go"

	"crypto/x509"
	"time"

	"crypto/tls"
	"regexp"

	boshhandler "github.com/cloudfoundry/bosh-agent/handler"
	boshplatform "github.com/cloudfoundry/bosh-agent/platform"
	boshsettings "github.com/cloudfoundry/bosh-agent/settings"
	bosherr "github.com/cloudfoundry/bosh-utils/errors"
	boshlog "github.com/cloudfoundry/bosh-utils/logger"
)

const (
	responseMaxLength = 1024 * 1024
	natsHandlerLogTag = "NATS Handler"
	natsMinRetryWait  = 2
	//natsMaxReconnectWait should be lower than the setting we have in BOSH for https://github.com/cloudfoundry/bosh/blob/main/src/bosh-director/lib/bosh/director/agent_client.rb#L44.
	natsMaxReconnectWait = 10 * time.Second
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate

type Handler interface {
	Run(boshhandler.Func) error
	Start(boshhandler.Func) error
	RegisterAdditionalFunc(boshhandler.Func)
	Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error
	Stop()
}

type NatsConnector func(url string, options ...nats.Option) (NatsConnection, error)

//counterfeiter:generate . NatsConnection

type NatsConnection interface {
	Close()
	Publish(subj string, data []byte) error
	Subscribe(subj string, cb nats.MsgHandler) (*nats.Subscription, error)
}

type natsHandler struct {
	settingsService boshsettings.Service
	connector       NatsConnector
	connection      NatsConnection
	platform        boshplatform.Platform

	handlerFuncs     []boshhandler.Func
	handlerFuncsLock sync.Mutex

	logger      boshlog.Logger
	auditLogger boshplatform.AuditLogger
	logTag      string
}

type ConnectionInfo struct {
	Addr      string
	IP        string
	Dial      func(network, address string) (net.Conn, error)
	TLSConfig *tls.Config
}

type ConnectionTLSInfo struct {
	CertPool              *x509.CertPool
	ClientCert            *tls.Certificate
	VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
}

func NewNatsHandler(
	settingsService boshsettings.Service,
	client NatsConnector,
	logger boshlog.Logger,
	platform boshplatform.Platform,
) Handler {
	return &natsHandler{
		settingsService: settingsService,
		connector:       client,
		platform:        platform,
		logger:          logger,
		logTag:          natsHandlerLogTag,
		auditLogger:     platform.GetAuditLogger(),
	}
}
func (h *natsHandler) arpClean() {
	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		h.logger.Error(h.logTag, "%v", bosherr.WrapError(err, "Getting connection info"))
	}
	err = h.platform.DeleteARPEntryWithIP(connectionInfo.IP)
	if err != nil {
		h.logger.Error(h.logTag, "Cleaning ip-mac address cache for: %s. Error: %v", connectionInfo.IP, err)
	}

	h.logger.Debug(h.logTag, "Cleaned ip-mac address cache for: %s.", connectionInfo.IP)
}

func (h *natsHandler) Run(handlerFunc boshhandler.Func) error {
	err := h.Start(handlerFunc)
	defer h.Stop()

	if err != nil {
		return bosherr.WrapError(err, "Starting nats handler")
	}

	h.runUntilInterrupted()
	return nil
}
func (h *natsHandler) Start(handlerFunc boshhandler.Func) error {
	h.RegisterAdditionalFunc(handlerFunc)

	connectionInfo, err := h.getConnectionInfo()
	if err != nil {
		return bosherr.WrapError(err, "Getting connection info")
	}
	h.logger.Info(h.logTag, "Attempting to connect to NATS")
	if net.ParseIP(connectionInfo.IP) != nil {
		h.arpClean()
	}
	var natsOptions = []nats.Option{
		nats.RetryOnFailedConnect(true),
		nats.DisconnectErrHandler(func(c *nats.Conn, err error) {
			h.logger.Debug(natsHandlerLogTag, "Nats disconnected with Error: %v", err.Error())
			h.logger.Debug(natsHandlerLogTag, "Attempting to reconnect: %v", c.IsReconnecting())
			for c.IsReconnecting() {
				h.arpClean()
				h.logger.Debug(natsHandlerLogTag, "Waiting to reconnect to nats.. Current attempt: %v, Connected: %v", c.Reconnects, c.IsConnected())
				time.Sleep(5 * time.Second)
			}
		}),
		nats.ReconnectHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Reconnected to %v", c.ConnectedAddr())
		}),
		nats.ClosedHandler(func(c *nats.Conn) {
			h.logger.Debug(natsHandlerLogTag, "Connection Closed with: %v", c.LastError().Error())
		}),
		nats.ErrorHandler(func(c *nats.Conn, s *nats.Subscription, err error) {
			h.logger.Debug(natsHandlerLogTag, err.Error())
		}),
		nats.CustomReconnectDelay(func(attempts int) time.Duration {
			exponentialReconnectWait := time.Duration(math.Pow(natsMinRetryWait, float64(attempts))) * time.Second
			if natsMaxReconnectWait > exponentialReconnectWait {
				h.logger.Debug(natsHandlerLogTag, "Increased reconnect to: %v", exponentialReconnectWait)
				return exponentialReconnectWait
			}
			h.logger.Debug(natsHandlerLogTag, "Increased reconnect to: %v", natsMaxReconnectWait)
			return natsMaxReconnectWait
		}),
		nats.MaxReconnects(-1),
		nats.Secure(connectionInfo.TLSConfig),
	}

	connection, err := h.connector(connectionInfo.Addr, natsOptions...)
	// just log this error. even if currently cannot connect to nats, we can eventually
	if err != nil {
		return bosherr.WrapError(err, "Connecting to NATS")
	}

	h.connection = connection

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("agent.%s", settings.AgentID)

	h.logger.Info(h.logTag, "Subscribing to %s", subject)

	_, err = h.connection.Subscribe(subject, func(natsMsg *nats.Msg) {
		// Do not lock handler funcs around possible network calls!
		h.handlerFuncsLock.Lock()
		handlerFuncs := h.handlerFuncs
		h.handlerFuncsLock.Unlock()

		for _, handlerFunc := range handlerFuncs {
			h.handleNatsMsg(natsMsg, handlerFunc)
		}
	})
	if err != nil {
		return bosherr.WrapErrorf(err, "Subscribing to %s", subject)
	}

	return nil
}

func (h *natsHandler) RegisterAdditionalFunc(handlerFunc boshhandler.Func) {
	// Currently not locking since RegisterAdditionalFunc
	// is not a primary way of adding handlerFunc.
	h.handlerFuncsLock.Lock()
	h.handlerFuncs = append(h.handlerFuncs, handlerFunc)
	h.handlerFuncsLock.Unlock()
}

func (h *natsHandler) Send(target boshhandler.Target, topic boshhandler.Topic, message interface{}) error {
	bytes, err := json.Marshal(message)
	if err != nil {
		return bosherr.WrapErrorf(err, "Marshalling message (target=%s, topic=%s): %#v", target, topic, message)
	}

	h.logger.Info(h.logTag, "Sending %s message '%s'", target, topic)
	h.logger.DebugWithDetails(h.logTag, "Message Payload", string(bytes))

	settings := h.settingsService.GetSettings()

	subject := fmt.Sprintf("%s.agent.%s.%s", target, topic, settings.AgentID)
	if h.connection != nil {
		return h.connection.Publish(subject, bytes)
	}
	return nil
}

func (h *natsHandler) Stop() {
	if h.connection != nil {
		h.connection.Close()
	}
}

func (h *natsHandler) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
	natsBoshInternalsRegexp := regexp.MustCompile(`^[a-zA-Z0-9*\-]*.nats.bosh-internal$`)
	for _, chain := range verifiedChains {
		if len(chain) == 0 {
			continue
		}
		commonName := chain[0].Subject.CommonName
		if natsBoshInternalsRegexp.MatchString(commonName) {
			return nil
		}
	}
	return errors.New("server Certificate CommonName does not match *.nats.bosh-internal")
}

func (h *natsHandler) handleNatsMsg(natsMsg *nats.Msg, handlerFunc boshhandler.Func) {
	respBytes, req, err := boshhandler.PerformHandlerWithJSON(
		natsMsg.Data,
		handlerFunc,
		responseMaxLength,
		h.logger,
	)

	if err != nil {
		h.logger.Error(h.logTag, "Running handler: %s", err)
		h.generateCEFLog(natsMsg, 7, err.Error())
		return
	}

	if len(respBytes) > 0 {
		err = h.connection.Publish(req.ReplyTo, respBytes)
		if err != nil {
			h.generateCEFLog(natsMsg, 7, err.Error())
			h.logger.Error(h.logTag, "Publishing to the client: %s", err.Error())
			return
		}
	}

	h.generateCEFLog(natsMsg, 1, "")
}

func (h *natsHandler) runUntilInterrupted() {
	defer h.connection.Close()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM)

	keepRunning := true
	for keepRunning {
		<-c
		keepRunning = false
	}
}

func (h *natsHandler) getConnectionInfo() (*ConnectionInfo, error) {
	settings := h.settingsService.GetSettings()

	connInfo := new(ConnectionInfo)
	connInfo.Addr = settings.GetMbusURL()
	natsURL, err := url.Parse(connInfo.Addr)
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing Nats URL")
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	connInfo.IP = hostSplit[0]

	connInfo.TLSConfig = &tls.Config{MinVersion: tls.VersionTLS12}

	caCert := settings.GetMbusCerts().CA
	if caCert != "" {
		connInfo.TLSConfig.RootCAs = x509.NewCertPool()
		if ok := connInfo.TLSConfig.RootCAs.AppendCertsFromPEM([]byte(caCert)); !ok {
			return nil, bosherr.Error("Failed to load Mbus CA cert")
		}
	}

	connInfo.TLSConfig.VerifyPeerCertificate = h.VerifyPeerCertificate

	clientCertificate, err := tls.X509KeyPair([]byte(settings.GetMbusCerts().Certificate), []byte(settings.GetMbusCerts().PrivateKey))
	if err != nil {
		return nil, bosherr.WrapError(err, "Parsing certificate and private key")
	}
	connInfo.TLSConfig.Certificates = []tls.Certificate{clientCertificate}

	return connInfo, nil
}

func (h *natsHandler) generateCEFLog(natsMsg *nats.Msg, severity int, statusReason string) {
	cef := boshhandler.NewCommonEventFormat()

	settings := h.settingsService.GetSettings()

	natsURL, err := url.Parse(settings.GetMbusURL())
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	hostSplit := strings.Split(natsURL.Host, ":")
	ip := hostSplit[0]
	payload := struct {
		Method  string `json:"method"`
		ReplyTo string `json:"reply_to"`
	}{}
	err = json.Unmarshal(natsMsg.Data, &payload)
	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
	}
	cefString, err := cef.ProduceNATSRequestEventLog(ip, hostSplit[1], payload.ReplyTo, payload.Method, severity, natsMsg.Subject, statusReason)

	if err != nil {
		h.logger.Error(natsHandlerLogTag, err.Error())
		return
	}

	if severity == 7 {
		h.auditLogger.Err(cefString)
		return
	}

	h.auditLogger.Debug(cefString)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"cred-alert/inflator"
	"cred-alert/kolsch"
	"cred-alert/mimetype"
	"cred-alert/scanners"
	"cred-alert/scanners/diffscanner"
	"cred-alert/scanners/dirscanner"
	"cred-alert/scanners/filescanner"
	"cred-alert/sniff"
	"cred-alert/sniff/credhandler"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	"code.cloudfoundry.org/lager"

	"github.com/jessevdk/go-flags"
	"github.com/mgutz/ansi"
)

type Opts struct {
	File string `short:"f" long:"file" description:"the file to scan" value-name:"FILE"`
	Diff bool   `long:"diff" description:"content to be scanned is a git diff"`
}

var red = ansi.ColorFunc("red+b")
var green = ansi.ColorFunc("green+b")

func main() {
	var opts Opts

	_, err := flags.ParseArgs(&opts, os.Args)
	if err != nil {
		os.Exit(1)
	}

	logger := kolsch.NewLogger()
	sniffer := sniff.NewDefaultSniffer()
	inflate := inflator.New()
	defer inflate.Close()

	handler := credhandler.New(func(logger lager.Logger, line scanners.Line) error {
		fmt.Printf("%s %s:%d\n", red("[CRED]"), line.Path, line.LineNumber)
		return nil
	})

	if opts.File != "" {
		start := time.Now()

		fh, err := os.Open(opts.File)
		if err != nil {
			log.Fatalln(err.Error())
		}

		destination, err := ioutil.TempDir("", "cred-alert-cli")
		if err != nil {
			log.Fatalln(err.Error())
		}

		defer os.RemoveAll(destination)

		br := bufio.NewReader(fh)
		mime, isArchive := mimetype.IsArchive(logger, br)
		if isArchive {
			fmt.Printf("Inflating archive... ")
			err := inflate.Inflate(logger, opts.File, destination)
			if err != nil {
				fmt.Printf("%s\n", red("FAILED"))
				log.Fatalln(err.Error())
			}
			fmt.Printf("%s\n", green("DONE"))

			dirScanner := dirscanner.New(handler.HandleViolation, sniffer)
			err = dirScanner.Scan(logger, destination)
			if err != nil {
				log.Fatalln(err.Error())
			}

			duration := time.Since(start)

			fmt.Println()
			fmt.Println("Scan complete!")
			fmt.Println()
			fmt.Println("Time taken:", duration)
			fmt.Println("Credentials found:", handler.CredentialCount())
			fmt.Println()
			fmt.Println("Any archive inflation errors can be found in: ", inflate.LogPath())
		} else {
			if strings.HasPrefix(mime, "text") {
				scanFile(logger, handler, sniffer, br, opts.File)
			}
		}
	} else if opts.Diff {
		handleDiff(logger, handler, opts)

	} else {
		scanFile(logger, handler, sniffer, os.Stdin, "STDIN")
	}

	if handler.CredentialsFound() {
		os.Exit(1)
	}
}

func scanFile(
	logger lager.Logger,
	handler *credhandler.Handler,
	sniffer sniff.Sniffer,
	f io.Reader,
	name string,
) {
	scanner := filescanner.New(f, name)
	sniffer.Sniff(logger, scanner, handler.HandleViolation)
}

func handleDiff(logger lager.Logger, handler *credhandler.Handler, opts Opts) {
	logger.Session("handle-diff")
	diff, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		logger.Error("read-error", err)
	}

	scanner := diffscanner.NewDiffScanner(string(diff))
	sniffer := sniff.NewDefaultSniffer()

	sniffer.Sniff(logger, scanner, handler.HandleViolation)
}
<commit_msg>CLI outputs time to inflate<commit_after>package main

import (
	"bufio"
	"cred-alert/inflator"
	"cred-alert/kolsch"
	"cred-alert/mimetype"
	"cred-alert/scanners"
	"cred-alert/scanners/diffscanner"
	"cred-alert/scanners/dirscanner"
	"cred-alert/scanners/filescanner"
	"cred-alert/sniff"
	"cred-alert/sniff/credhandler"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"

	"code.cloudfoundry.org/lager"

	"github.com/jessevdk/go-flags"
	"github.com/mgutz/ansi"
)

type Opts struct {
	File string `short:"f" long:"file" description:"the file to scan" value-name:"FILE"`
	Diff bool   `long:"diff" description:"content to be scanned is a git diff"`
}

var red = ansi.ColorFunc("red+b")
var green = ansi.ColorFunc("green+b")

func main() {
	var opts Opts

	_, err := flags.ParseArgs(&opts, os.Args)
	if err != nil {
		os.Exit(1)
	}

	logger := kolsch.NewLogger()
	sniffer := sniff.NewDefaultSniffer()
	inflate := inflator.New()
	defer inflate.Close()

	handler := credhandler.New(func(logger lager.Logger, line scanners.Line) error {
		fmt.Printf("%s %s:%d\n", red("[CRED]"), line.Path, line.LineNumber)
		return nil
	})

	if opts.File != "" {
		fh, err := os.Open(opts.File)
		if err != nil {
			log.Fatalln(err.Error())
		}

		destination, err := ioutil.TempDir("", "cred-alert-cli")
		if err != nil {
			log.Fatalln(err.Error())
		}

		defer os.RemoveAll(destination)

		br := bufio.NewReader(fh)
		mime, isArchive := mimetype.IsArchive(logger, br)
		if isArchive {
			inflateStart := time.Now()
			fmt.Printf("Inflating archive to %s ... ", destination)
			err := inflate.Inflate(logger, opts.File, destination)
			if err != nil {
				fmt.Printf("%s\n", red("FAILED"))
				log.Fatalln(err.Error())
			}
			fmt.Printf("%s (%s)\n", green("DONE"), time.Since(inflateStart))

			scanStart := time.Now()
			dirScanner := dirscanner.New(handler.HandleViolation, sniffer)
			err = dirScanner.Scan(logger, destination)
			if err != nil {
				log.Fatalln(err.Error())
			}

			fmt.Println()
			fmt.Println("Scan complete!")
			fmt.Println()
			fmt.Println("Time taken:", time.Since(scanStart))
			fmt.Println("Credentials found:", handler.CredentialCount())
			fmt.Println()
			fmt.Println("Any archive inflation errors can be found in: ", inflate.LogPath())
		} else {
			if strings.HasPrefix(mime, "text") {
				scanFile(logger, handler, sniffer, br, opts.File)
			}
		}
	} else if opts.Diff {
		handleDiff(logger, handler, opts)

	} else {
		scanFile(logger, handler, sniffer, os.Stdin, "STDIN")
	}

	if handler.CredentialsFound() {
		os.Exit(1)
	}
}

func scanFile(
	logger lager.Logger,
	handler *credhandler.Handler,
	sniffer sniff.Sniffer,
	f io.Reader,
	name string,
) {
	scanner := filescanner.New(f, name)
	sniffer.Sniff(logger, scanner, handler.HandleViolation)
}

func handleDiff(logger lager.Logger, handler *credhandler.Handler, opts Opts) {
	logger.Session("handle-diff")
	diff, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		logger.Error("read-error", err)
	}

	scanner := diffscanner.NewDiffScanner(string(diff))
	sniffer := sniff.NewDefaultSniffer()

	sniffer.Sniff(logger, scanner, handler.HandleViolation)
}
<|endoftext|>


<commit_before>package beanspike

import (
	"errors"
	"fmt"
	"strings"

	as "github.com/aerospike/aerospike-client-go"
	ast "github.com/aerospike/aerospike-client-go/types"
)

func (conn *Conn) newJobId() (int64, error) {
	key, err := as.NewKey(AerospikeNamespace, AerospikeMetadataSet, "seq")
	if err != nil {
		return 0, err
	}

	bin := as.NewBin("last", as.NewLongValue(1))
	record, err := conn.aerospike.Operate(as.NewWritePolicy(0, 0), key, as.AddOp(bin), as.GetOp())
	if err != nil {
		return 0, err
	}

	// This type conversion seem required as the value appears to be a
	// int instead of an int64
	id := int64(record.Bins[bin.Name].(int))
	return id, nil
}

// Note, index limits may cause this to fail
func (conn *Conn) Use(name string) (*Tube, error) {
	if conn == nil {
		return nil, errors.New("Aerospike connection not established")
	}

	name = strings.Trim(name, " ")
	if name == "" {
		return nil, errors.New("Tube name must not be blank")
	}

	if name == AerospikeMetadataSet {
		return nil, errors.New(fmt.Sprintf("Tube name %v is reserved", name))
	}

	task, err := conn.aerospike.CreateIndex(nil, AerospikeNamespace, name, "idx_tube_"+name+"_"+AerospikeNameStatus, AerospikeNameStatus, as.STRING)
	if err != nil {
		if ae, ok := err.(ast.AerospikeError); ok && ae.ResultCode() == ast.INDEX_FOUND {
			// skipping index creation
			// println("Skipping index creation")
		} else {
			return nil, err
		}
	}

	if task == nil {
		//TODO: Check that this is ok
	} else {
		for ierr := range task.OnComplete() {
			if ierr != nil {
				return nil, ierr
			}
		}
	}
	//TODO: Literals
	tube := new(Tube)
	tube.Conn = conn
	tube.Name = name
	tube.first = true

	return tube, nil
}

// Any Tubes that reference this name
// should be discarded after this operation
func (conn *Conn) Delete(name string) error {
	if conn == nil {
		return errors.New("Aerospike connection not established")
	}

	client := conn.aerospike

	// No Truncate for sets yet, scan and purge
	recordset, err := client.ScanAll(nil, AerospikeNamespace, name)

	if err != nil {
		return err
	}

	defer recordset.Close()

	for res := range recordset.Results() {
		if res.Err != nil {
			return res.Err
		}
		key := res.Record.Key

		_, err = client.Delete(nil, key)
		if err != nil {
			return err
		}
	}

	tk, _ := as.NewKey(AerospikeNamespace, AerospikeMetadataSet, name+":"+AerospikeKeySuffixTtr)
	client.Delete(nil, tk)

	dk, _ := as.NewKey(AerospikeNamespace, AerospikeMetadataSet, name+":"+AerospikeKeySuffixDelayed)
	client.Delete(nil, dk)

	err = client.DropIndex(nil, AerospikeNamespace, name, "idx_tube_"+name+"_"+AerospikeNameStatus)

	if err != nil {
		return err
	}

	return nil
}
<commit_msg>cache created tubes, otherwise we end up bombarding aerospike if conn.Use() is not used correctly<commit_after>package beanspike

import (
	"errors"
	"fmt"
	"strings"
	"sync"

	as "github.com/aerospike/aerospike-client-go"
	ast "github.com/aerospike/aerospike-client-go/types"
)

var rwMutex = sync.RWMutex{}

var tubesMap = map[string]*Tube{}

func (conn *Conn) newJobId() (int64, error) {
	key, err := as.NewKey(AerospikeNamespace, AerospikeMetadataSet, "seq")
	if err != nil {
		return 0, err
	}

	bin := as.NewBin("last", as.NewLongValue(1))
	record, err := conn.aerospike.Operate(as.NewWritePolicy(0, 0), key, as.AddOp(bin), as.GetOp())
	if err != nil {
		return 0, err
	}

	// This type conversion seem required as the value appears to be a
	// int instead of an int64
	id := int64(record.Bins[bin.Name].(int))
	return id, nil
}

// Note, index limits may cause this to fail
func (conn *Conn) Use(name string) (*Tube, error) {
	if conn == nil {
		return nil, errors.New("Aerospike connection not established")
	}

	name = strings.Trim(name, " ")
	if name == "" {
		return nil, errors.New("Tube name must not be blank")
	}

	if name == AerospikeMetadataSet {
		return nil, errors.New(fmt.Sprintf("Tube name %v is reserved", name))
	}

	rwMutex.RLock()
	t := tubesMap[name]
	if t != nil {
		rwMutex.RUnlock()
		return t, nil
	}
	rwMutex.RUnlock()

	rwMutex.Lock()
	defer func() {
		rwMutex.Unlock()
	}()

	t = tubesMap[name]
	if t != nil {
		return t, nil
	}

	task, err := conn.aerospike.CreateIndex(nil, AerospikeNamespace, name, "idx_tube_"+name+"_"+AerospikeNameStatus, AerospikeNameStatus, as.STRING)
	if err != nil {
		if ae, ok := err.(ast.AerospikeError); ok && ae.ResultCode() == ast.INDEX_FOUND {
			// skipping index creation
			// println("Skipping index creation")
		} else {
			return nil, err
		}
	}

	if task == nil {
		//TODO: Check that this is ok
	} else {
		for ierr := range task.OnComplete() {
			if ierr != nil {
				return nil, ierr
			}
		}
	}
	//TODO: Literals
	tube := new(Tube)
	tube.Conn = conn
	tube.Name = name
	tube.first = true

	tubesMap[name] = tube
	return tube, nil
}

// Any Tubes that reference this name
// should be discarded after this operation
func (conn *Conn) Delete(name string) error {
	if conn == nil {
		return errors.New("Aerospike connection not established")
	}

	client := conn.aerospike

	// No Truncate for sets yet, scan and purge
	recordset, err := client.ScanAll(nil, AerospikeNamespace, name)

	if err != nil {
		return err
	}

	defer recordset.Close()

	for res := range recordset.Results() {
		if res.Err != nil {
			return res.Err
		}
		key := res.Record.Key

		_, err = client.Delete(nil, key)
		if err != nil {
			return err
		}
	}

	tk, _ := as.NewKey(AerospikeNamespace, AerospikeMetadataSet, name+":"+AerospikeKeySuffixTtr)
	client.Delete(nil, tk)

	dk, _ := as.NewKey(AerospikeNamespace, AerospikeMetadataSet, name+":"+AerospikeKeySuffixDelayed)
	client.Delete(nil, dk)

	err = client.DropIndex(nil, AerospikeNamespace, name, "idx_tube_"+name+"_"+AerospikeNameStatus)

	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package generic

import (
	"golang.org/x/net/http2"

	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/spec"
)

func Data() *spec.TestGroup {
	tg := NewTestGroup("3.1", "DATA")

	// RFC7540, 6.1:
	// DATA frames (type=0x0) convey arbitrary, variable-length
	// sequences of octets associated with a stream. One or more
	// DATA frames are used, for instance, to carry HTTP request
	// or response payloads.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "Sends a DATA frame",
		Requirement: "The endpoint MUST accept DATA frame.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			headers[0].Value = "POST"

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyHeadersFrame(conn, streamID)
		},
	})

	// RFC7540, 6.1:
	// DATA frames (type=0x0) convey arbitrary, variable-length
	// sequences of octets associated with a stream. One or more
	// DATA frames are used, for instance, to carry HTTP request
	// or response payloads.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "Sends multiple DATA frames",
		Requirement: "The endpoint MUST accept multiple DATA frames.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			headers[0].Value = "POST"

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteData(streamID, false, []byte("test"))
			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyHeadersFrame(conn, streamID)
		},
	})

	// RFC7540, 6.1:
	// DATA frames MAY also contain padding. Padding can be added to
	// DATA frames to obscure the size of messages. Padding is a
	// security feature; see Section 10.7.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "Sends a DATA frame with padding",
		Requirement: "The endpoint MUST accept DATA frame with padding.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			headers[0].Value = "POST"

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteDataPadded(streamID, true, []byte("test"), []byte("padded"))

			return spec.VerifyHeadersFrame(conn, streamID)
		},
	})

	return tg
}
<commit_msg>Send null bytes as padding of DATA frame<commit_after>package generic

import (
	"golang.org/x/net/http2"

	"github.com/summerwind/h2spec/config"
	"github.com/summerwind/h2spec/spec"
)

func Data() *spec.TestGroup {
	tg := NewTestGroup("3.1", "DATA")

	// RFC7540, 6.1:
	// DATA frames (type=0x0) convey arbitrary, variable-length
	// sequences of octets associated with a stream. One or more
	// DATA frames are used, for instance, to carry HTTP request
	// or response payloads.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "Sends a DATA frame",
		Requirement: "The endpoint MUST accept DATA frame.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			headers[0].Value = "POST"

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyHeadersFrame(conn, streamID)
		},
	})

	// RFC7540, 6.1:
	// DATA frames (type=0x0) convey arbitrary, variable-length
	// sequences of octets associated with a stream. One or more
	// DATA frames are used, for instance, to carry HTTP request
	// or response payloads.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "Sends multiple DATA frames",
		Requirement: "The endpoint MUST accept multiple DATA frames.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			headers[0].Value = "POST"

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteData(streamID, false, []byte("test"))
			conn.WriteData(streamID, true, []byte("test"))

			return spec.VerifyHeadersFrame(conn, streamID)
		},
	})

	// RFC7540, 6.1:
	// DATA frames MAY also contain padding. Padding can be added to
	// DATA frames to obscure the size of messages. Padding is a
	// security feature; see Section 10.7.
	tg.AddTestCase(&spec.TestCase{
		Desc:        "Sends a DATA frame with padding",
		Requirement: "The endpoint MUST accept DATA frame with padding.",
		Run: func(c *config.Config, conn *spec.Conn) error {
			var streamID uint32 = 1

			err := conn.Handshake()
			if err != nil {
				return err
			}

			headers := spec.CommonHeaders(c)
			headers[0].Value = "POST"

			hp := http2.HeadersFrameParam{
				StreamID:      streamID,
				EndStream:     false,
				EndHeaders:    true,
				BlockFragment: conn.EncodeHeaders(headers),
			}

			conn.WriteHeaders(hp)
			conn.WriteDataPadded(streamID, true, []byte("test"), []byte("\x00\x00\x00\x00\x00"))

			return spec.VerifyHeadersFrame(conn, streamID)
		},
	})

	return tg
}
<|endoftext|>