<commit_before>package stripe

import (
	"encoding/json"
)

// CheckoutSessionSubmitType is the list of allowed values for the `submit_type`
// of a Session.
type CheckoutSessionSubmitType string

// List of values that CheckoutSessionSubmitType can take.
const (
	CheckoutSessionSubmitTypeAuto   CheckoutSessionSubmitType = "auto"
	CheckoutSessionSubmitTypeBook   CheckoutSessionSubmitType = "book"
	CheckoutSessionSubmitTypeDonate CheckoutSessionSubmitType = "donate"
	CheckoutSessionSubmitTypePay    CheckoutSessionSubmitType = "pay"
)

// CheckoutSessionDisplayItemType is the list of allowed values for the display item type.
type CheckoutSessionDisplayItemType string

// List of values that CheckoutSessionDisplayItemType can take.
const (
	CheckoutSessionDisplayItemTypeCustom CheckoutSessionDisplayItemType = "custom"
	CheckoutSessionDisplayItemTypePlan   CheckoutSessionDisplayItemType = "plan"
	CheckoutSessionDisplayItemTypeSKU    CheckoutSessionDisplayItemType = "sku"
)

// CheckoutSessionMode is the list of allowed values for the mode on a Session.
type CheckoutSessionMode string

// List of values that CheckoutSessionMode can take.
const (
	CheckoutSessionModePayment      CheckoutSessionMode = "payment"
	CheckoutSessionModeSetup        CheckoutSessionMode = "setup"
	CheckoutSessionModeSubscription CheckoutSessionMode = "subscription"
)

// CheckoutSessionLineItemParams is the set of parameters allowed for a line item
// on a checkout session.
type CheckoutSessionLineItemParams struct {
	Amount      *int64    `form:"amount"`
	Currency    *string   `form:"currency"`
	Description *string   `form:"description"`
	Images      []*string `form:"images"`
	Name        *string   `form:"name"`
	Quantity    *int64    `form:"quantity"`
	TaxRates    []*string `form:"tax_rates"`
}

// CheckoutSessionPaymentIntentDataTransferDataParams is the set of parameters allowed for the
// transfer_data hash.
type CheckoutSessionPaymentIntentDataTransferDataParams struct {
	Amount      *int64  `form:"amount"`
	Destination *string `form:"destination"`
}

// CheckoutSessionPaymentIntentDataParams is the set of parameters allowed for the
// payment intent creation on a checkout session.
type CheckoutSessionPaymentIntentDataParams struct {
	Params                    `form:"*"`
	ApplicationFeeAmount      *int64                                              `form:"application_fee_amount"`
	CaptureMethod             *string                                             `form:"capture_method"`
	Description               *string                                             `form:"description"`
	OnBehalfOf                *string                                             `form:"on_behalf_of"`
	ReceiptEmail              *string                                             `form:"receipt_email"`
	SetupFutureUsage          *string                                             `form:"setup_future_usage"`
	Shipping                  *ShippingDetailsParams                              `form:"shipping"`
	StatementDescriptor       *string                                             `form:"statement_descriptor"`
	StatementDescriptorSuffix *string                                             `form:"statement_descriptor_suffix"`
	TransferData              *CheckoutSessionPaymentIntentDataTransferDataParams `form:"transfer_data"`
}

// CheckoutSessionSetupIntentDataParams is the set of parameters allowed for the setup intent
// creation on a checkout session.
type CheckoutSessionSetupIntentDataParams struct {
	Params      `form:"*"`
	Description *string `form:"description"`
	OnBehalfOf  *string `form:"on_behalf_of"`
}

// CheckoutSessionSubscriptionDataItemsParams is the set of parameters allowed for one item on a
// checkout session associated with a subscription.
type CheckoutSessionSubscriptionDataItemsParams struct {
	Plan     *string   `form:"plan"`
	Quantity *int64    `form:"quantity"`
	TaxRates []*string `form:"tax_rates"`
}

// CheckoutSessionSubscriptionDataParams is the set of parameters allowed for the subscription
// creation on a checkout session.
type CheckoutSessionSubscriptionDataParams struct {
	Params                `form:"*"`
	ApplicationFeePercent *float64                                      `form:"application_fee_percent"`
	DefaultTaxRates       []*string                                     `form:"default_tax_rates"`
	Items                 []*CheckoutSessionSubscriptionDataItemsParams `form:"items"`
	TrialEnd              *int64                                        `form:"trial_end"`
	TrialFromPlan         *bool                                         `form:"trial_from_plan"`
	TrialPeriodDays       *int64                                        `form:"trial_period_days"`
}

// CheckoutSessionParams is the set of parameters that can be used when creating
// a checkout session.
// For more details see https://stripe.com/docs/api/checkout/sessions/create
type CheckoutSessionParams struct {
	Params                   `form:"*"`
	BillingAddressCollection *string                                 `form:"billing_address_collection"`
	CancelURL                *string                                 `form:"cancel_url"`
	ClientReferenceID        *string                                 `form:"client_reference_id"`
	Customer                 *string                                 `form:"customer"`
	CustomerEmail            *string                                 `form:"customer_email"`
	LineItems                []*CheckoutSessionLineItemParams        `form:"line_items"`
	Locale                   *string                                 `form:"locale"`
	Mode                     *string                                 `form:"mode"`
	PaymentIntentData        *CheckoutSessionPaymentIntentDataParams `form:"payment_intent_data"`
	PaymentMethodTypes       []*string                               `form:"payment_method_types"`
	SetupIntentData          *CheckoutSessionSetupIntentDataParams   `form:"setup_intent_data"`
	SubscriptionData         *CheckoutSessionSubscriptionDataParams  `form:"subscription_data"`
	SubmitType               *string                                 `form:"submit_type"`
	SuccessURL               *string                                 `form:"success_url"`
}

// CheckoutSessionListParams is the set of parameters that can be
// used when listing sessions.
// For more details see: https://stripe.com/docs/api/checkout/sessions/list
type CheckoutSessionListParams struct {
	ListParams    `form:"*"`
	PaymentIntent *string `form:"payment_intent"`
	Subscription  *string `form:"subscription"`
}

// CheckoutSessionDisplayItemCustom represents an item of type custom in a checkout session
type CheckoutSessionDisplayItemCustom struct {
	Description string   `json:"description"`
	Images      []string `json:"images"`
	Name        string   `json:"name"`
}

// CheckoutSessionDisplayItem represents one of the items in a checkout session.
type CheckoutSessionDisplayItem struct {
	Amount   int64                             `json:"amount"`
	Currency Currency                          `json:"currency"`
	Custom   *CheckoutSessionDisplayItemCustom `json:"custom"`
	Quantity int64                             `json:"quantity"`
	Plan     *Plan                             `json:"plan"`
	SKU      *SKU                              `json:"sku"`
	Type     CheckoutSessionDisplayItemType    `json:"type"`
}

// CheckoutSession is the resource representing a Stripe checkout session.
// For more details see https://stripe.com/docs/api/checkout/sessions/object
type CheckoutSession struct {
	CancelURL          string                        `json:"cancel_url"`
	ClientReferenceID  string                        `json:"client_reference_id"`
	Customer           *Customer                     `json:"customer"`
	CustomerEmail      string                        `json:"customer_email"`
	Deleted            bool                          `json:"deleted"`
	DisplayItems       []*CheckoutSessionDisplayItem `json:"display_items"`
	ID                 string                        `json:"id"`
	Livemode           bool                          `json:"livemode"`
	Locale             string                        `json:"locale"`
	Metadata           map[string]string             `json:"metadata"`
	Mode               CheckoutSessionMode           `json:"mode"`
	Object             string                        `json:"object"`
	PaymentIntent      *PaymentIntent                `json:"payment_intent"`
	PaymentMethodTypes []string                      `json:"payment_method_types"`
	SetupIntent        *SetupIntent                  `json:"setup_intent"`
	Subscription       *Subscription                 `json:"subscription"`
	SubmitType         CheckoutSessionSubmitType     `json:"submit_type"`
	SuccessURL         string                        `json:"success_url"`
}

// CheckoutSessionList is a list of sessions as retrieved from a list endpoint.
type CheckoutSessionList struct {
	ListMeta
	Data []*CheckoutSession `json:"data"`
}

// UnmarshalJSON handles deserialization of a checkout session.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (p *CheckoutSession) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		p.ID = id
		return nil
	}

	type session CheckoutSession
	var v session
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*p = CheckoutSession(v)
	return nil
}
<commit_msg>Add support for `Shipping` and `ShippingAddressCollection` on Checkout Session<commit_after>package stripe

import (
	"encoding/json"
)

// CheckoutSessionSubmitType is the list of allowed values for the `submit_type`
// of a Session.
type CheckoutSessionSubmitType string

// List of values that CheckoutSessionSubmitType can take.
const (
	CheckoutSessionSubmitTypeAuto   CheckoutSessionSubmitType = "auto"
	CheckoutSessionSubmitTypeBook   CheckoutSessionSubmitType = "book"
	CheckoutSessionSubmitTypeDonate CheckoutSessionSubmitType = "donate"
	CheckoutSessionSubmitTypePay    CheckoutSessionSubmitType = "pay"
)

// CheckoutSessionDisplayItemType is the list of allowed values for the display item type.
type CheckoutSessionDisplayItemType string

// List of values that CheckoutSessionDisplayItemType can take.
const (
	CheckoutSessionDisplayItemTypeCustom CheckoutSessionDisplayItemType = "custom"
	CheckoutSessionDisplayItemTypePlan   CheckoutSessionDisplayItemType = "plan"
	CheckoutSessionDisplayItemTypeSKU    CheckoutSessionDisplayItemType = "sku"
)

// CheckoutSessionMode is the list of allowed values for the mode on a Session.
type CheckoutSessionMode string

// List of values that CheckoutSessionMode can take.
const (
	CheckoutSessionModePayment      CheckoutSessionMode = "payment"
	CheckoutSessionModeSetup        CheckoutSessionMode = "setup"
	CheckoutSessionModeSubscription CheckoutSessionMode = "subscription"
)

// CheckoutSessionLineItemParams is the set of parameters allowed for a line item
// on a checkout session.
type CheckoutSessionLineItemParams struct {
	Amount      *int64    `form:"amount"`
	Currency    *string   `form:"currency"`
	Description *string   `form:"description"`
	Images      []*string `form:"images"`
	Name        *string   `form:"name"`
	Quantity    *int64    `form:"quantity"`
	TaxRates    []*string `form:"tax_rates"`
}

// CheckoutSessionPaymentIntentDataTransferDataParams is the set of parameters allowed for the
// transfer_data hash.
type CheckoutSessionPaymentIntentDataTransferDataParams struct {
	Amount      *int64  `form:"amount"`
	Destination *string `form:"destination"`
}

// CheckoutSessionPaymentIntentDataParams is the set of parameters allowed for the
// payment intent creation on a checkout session.
type CheckoutSessionPaymentIntentDataParams struct {
	Params                    `form:"*"`
	ApplicationFeeAmount      *int64                                              `form:"application_fee_amount"`
	CaptureMethod             *string                                             `form:"capture_method"`
	Description               *string                                             `form:"description"`
	OnBehalfOf                *string                                             `form:"on_behalf_of"`
	ReceiptEmail              *string                                             `form:"receipt_email"`
	SetupFutureUsage          *string                                             `form:"setup_future_usage"`
	Shipping                  *ShippingDetailsParams                              `form:"shipping"`
	StatementDescriptor       *string                                             `form:"statement_descriptor"`
	StatementDescriptorSuffix *string                                             `form:"statement_descriptor_suffix"`
	TransferData              *CheckoutSessionPaymentIntentDataTransferDataParams `form:"transfer_data"`
}

// CheckoutSessionSetupIntentDataParams is the set of parameters allowed for the setup intent
// creation on a checkout session.
type CheckoutSessionSetupIntentDataParams struct {
	Params      `form:"*"`
	Description *string `form:"description"`
	OnBehalfOf  *string `form:"on_behalf_of"`
}

// CheckoutSessionShippingAddressCollectionParams is the set of parameters allowed for the
// shipping address collection.
type CheckoutSessionShippingAddressCollectionParams struct {
	AllowedCountries []*string `form:"allowed_countries"`
}

// CheckoutSessionSubscriptionDataItemsParams is the set of parameters allowed for one item on a
// checkout session associated with a subscription.
type CheckoutSessionSubscriptionDataItemsParams struct {
	Plan     *string   `form:"plan"`
	Quantity *int64    `form:"quantity"`
	TaxRates []*string `form:"tax_rates"`
}

// CheckoutSessionSubscriptionDataParams is the set of parameters allowed for the subscription
// creation on a checkout session.
type CheckoutSessionSubscriptionDataParams struct {
	Params                `form:"*"`
	ApplicationFeePercent *float64                                      `form:"application_fee_percent"`
	DefaultTaxRates       []*string                                     `form:"default_tax_rates"`
	Items                 []*CheckoutSessionSubscriptionDataItemsParams `form:"items"`
	TrialEnd              *int64                                        `form:"trial_end"`
	TrialFromPlan         *bool                                         `form:"trial_from_plan"`
	TrialPeriodDays       *int64                                        `form:"trial_period_days"`
}

// CheckoutSessionParams is the set of parameters that can be used when creating
// a checkout session.
// For more details see https://stripe.com/docs/api/checkout/sessions/create
type CheckoutSessionParams struct {
	Params                    `form:"*"`
	BillingAddressCollection  *string                                         `form:"billing_address_collection"`
	CancelURL                 *string                                         `form:"cancel_url"`
	ClientReferenceID         *string                                         `form:"client_reference_id"`
	Customer                  *string                                         `form:"customer"`
	CustomerEmail             *string                                         `form:"customer_email"`
	LineItems                 []*CheckoutSessionLineItemParams                `form:"line_items"`
	Locale                    *string                                         `form:"locale"`
	Mode                      *string                                         `form:"mode"`
	PaymentIntentData         *CheckoutSessionPaymentIntentDataParams         `form:"payment_intent_data"`
	PaymentMethodTypes        []*string                                       `form:"payment_method_types"`
	SetupIntentData           *CheckoutSessionSetupIntentDataParams           `form:"setup_intent_data"`
	ShippingAddressCollection *CheckoutSessionShippingAddressCollectionParams `form:"shipping_address_collection"`
	SubscriptionData          *CheckoutSessionSubscriptionDataParams          `form:"subscription_data"`
	SubmitType                *string                                         `form:"submit_type"`
	SuccessURL                *string                                         `form:"success_url"`
}

// CheckoutSessionListParams is the set of parameters that can be
// used when listing sessions.
// For more details see: https://stripe.com/docs/api/checkout/sessions/list
type CheckoutSessionListParams struct {
	ListParams    `form:"*"`
	PaymentIntent *string `form:"payment_intent"`
	Subscription  *string `form:"subscription"`
}

// CheckoutSessionDisplayItemCustom represents an item of type custom in a checkout session
type CheckoutSessionDisplayItemCustom struct {
	Description string   `json:"description"`
	Images      []string `json:"images"`
	Name        string   `json:"name"`
}

// CheckoutSessionDisplayItem represents one of the items in a checkout session.
type CheckoutSessionDisplayItem struct {
	Amount   int64                             `json:"amount"`
	Currency Currency                          `json:"currency"`
	Custom   *CheckoutSessionDisplayItemCustom `json:"custom"`
	Quantity int64                             `json:"quantity"`
	Plan     *Plan                             `json:"plan"`
	SKU      *SKU                              `json:"sku"`
	Type     CheckoutSessionDisplayItemType    `json:"type"`
}

// CheckoutSessionShippingAddressCollection is the set of parameters allowed for the
// shipping address collection.
type CheckoutSessionShippingAddressCollection struct {
	AllowedCountries []string `json:"allowed_countries"`
}

// CheckoutSession is the resource representing a Stripe checkout session.
// For more details see https://stripe.com/docs/api/checkout/sessions/object
type CheckoutSession struct {
	CancelURL                 string                                    `json:"cancel_url"`
	ClientReferenceID         string                                    `json:"client_reference_id"`
	Customer                  *Customer                                 `json:"customer"`
	CustomerEmail             string                                    `json:"customer_email"`
	Deleted                   bool                                      `json:"deleted"`
	DisplayItems              []*CheckoutSessionDisplayItem             `json:"display_items"`
	ID                        string                                    `json:"id"`
	Livemode                  bool                                      `json:"livemode"`
	Locale                    string                                    `json:"locale"`
	Metadata                  map[string]string                         `json:"metadata"`
	Mode                      CheckoutSessionMode                       `json:"mode"`
	Object                    string                                    `json:"object"`
	PaymentIntent             *PaymentIntent                            `json:"payment_intent"`
	PaymentMethodTypes        []string                                  `json:"payment_method_types"`
	SetupIntent               *SetupIntent                              `json:"setup_intent"`
	Shipping                  *ShippingDetails                          `json:"shipping"`
	ShippingAddressCollection *CheckoutSessionShippingAddressCollection `json:"shipping_address_collection"`
	Subscription              *Subscription                             `json:"subscription"`
	SubmitType                CheckoutSessionSubmitType                 `json:"submit_type"`
	SuccessURL                string                                    `json:"success_url"`
}

// CheckoutSessionList is a list of sessions as retrieved from a list endpoint.
type CheckoutSessionList struct {
	ListMeta
	Data []*CheckoutSession `json:"data"`
}

// UnmarshalJSON handles deserialization of a checkout session.
// This custom unmarshaling is needed because the resulting
// property may be an id or the full struct if it was expanded.
func (p *CheckoutSession) UnmarshalJSON(data []byte) error {
	if id, ok := ParseID(data); ok {
		p.ID = id
		return nil
	}

	type session CheckoutSession
	var v session
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}

	*p = CheckoutSession(v)
	return nil
}
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"io/ioutil"
	"os"
	"strings"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	. "go.polydawn.net/hitch/lib/errcat"
	. "go.polydawn.net/hitch/lib/testutil"
)

func Test(t *testing.T) {
	ui := UI{
		nil,
		ioutil.Discard,
		ioutil.Discard,
	}
	Convey("Initialization / db-finding scenarios", t, func() {
		WithChdirTmpdir(func() {
			Convey("init happy path", func() {
				So(Init(ui), ShouldErrorWith, nil)
			})
			Convey("init repeated should fail", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(Init(ui), ShouldErrorWith, ErrInProgress)
			})
			Convey("init inside another db should fail", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(os.Mkdir("deeper", 0755), ShouldBeNil)
				So(os.Chdir("deeper"), ShouldBeNil)
				So(Init(ui), ShouldErrorWith, ErrInProgress)
			})
			Convey("catalog-create finds db in cwd", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
			})
			Convey("catalog-create finds db when deeper", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(os.Mkdir("deeper", 0755), ShouldBeNil)
				So(os.Chdir("deeper"), ShouldBeNil)
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
			})
		})
	})
	Convey("Release staging operations", t, func() {
		WithChdirTmpdir(func() {
			Must(Init(ui))

			Convey("starting a release to a nonexistent catalog should be rejected", func() {
				So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, ErrDataNotFound)
			})
			Convey("setting up a simple release with several items, happy path", func() {
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
				So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, nil)
				So(ReleaseAddItem(ui, "label-foo", "tar:asdfasdf"), ShouldErrorWith, nil)
				So(ReleaseAddItem(ui, "label-bar", "tar:asdfasdf"), ShouldErrorWith, nil)
				Convey("adding a clearly invalid wareID should be rejected", func() {
					So(ReleaseAddItem(ui, "label-bar", "not a ware id!"), ShouldErrorWith, ErrBadArgs)
				})
				// TODO : overwrite detection not implemented yet!
				//	Convey("overwriting an item should be rejected", func() {
				//		So(ReleaseAddItem(ui, "label-bar", "tar:asdfasdf"), ShouldErrorWith, ErrOverwrite)
				//	})
				Convey("...should be able to commit!", func() {
					So(ReleaseCommit(ui), ShouldErrorWith, nil)
					Convey("starting a new release with a new name should fly", func() {
						So(ReleaseStart(ui, "cn", "rn-v200"), ShouldErrorWith, nil)
					})
					Convey("starting a new release with the same name should be rejected", func() {
						So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, ErrNameCollision)
					})
				})
			})
			Convey("starting a release twice should be rejected", func() {
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
				So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, nil)
				So(ReleaseStart(ui, "cn", "zw"), ShouldErrorWith, ErrInProgress)
				// TODO : `hitch release reset` not implemented yet!
				//	Convey("reseting it should allow starting over", func() {
				//		So(ReleaseReset(ui), ShouldErrorWith, nil)
				//		So(ReleaseStart(ui, "cn", "zw"), ShouldErrorWith, nil)
				//	})
			})
		})
	})
	Convey("Show command operations", t, func() {
		WithChdirTmpdir(func() {
			Must(Init(ui))

			Convey("given a sizable catalog", func() {
				Must(CatalogCreate(ui, "cn"))
				Must(ReleaseStart(ui, "cn", "v0.1"))
				Must(ReleaseAddItem(ui, "label-foo", "tar:asdfasdf"))
				Must(ReleaseAddItem(ui, "label-bar", "tar:asdfqwer"))
				Must(ReleaseCommit(ui))
				Must(ReleaseStart(ui, "cn", "v0.2"))
				Must(ReleaseAddItem(ui, "label-foo", "tar:qwerasdf"))
				Must(ReleaseAddItem(ui, "label-bar", "tar:qwerqwer"))
				Must(ReleaseAddItem(ui, "label-qux", "tar:qwerzxcv"))
				Must(ReleaseCommit(ui))

				Convey("`hitch show <catalog>` should say a *lot*", func() {
					output, err := grabOutput(func(ui UI) error {
						return Show(ui, "cn")
					})
					So(err, ShouldErrorWith, nil)
					So(output, ShouldContainSubstring, `"cn"`)              // catalogs contain their own name
					So(output, ShouldContainSubstring, `"v0.1"`)            // both release names should appear
					So(output, ShouldContainSubstring, `"v0.2"`)            // both release names should appear
					So(output, ShouldContainSubstring, `"tar:asdfasdf"`)    // wareIDs in the first release should appear -- showing a catalog is *loud*!
					So(output, ShouldContainSubstring, `"tar:qwerqwer"`)    // wareIDs in the second release should appear -- showing a catalog is *loud*!
					So(strings.Count(output, `"metadata"`), ShouldEqual, 2) // keyword "metadata" should appear once per release entry
				})
				Convey("`hitch show <catalog>` requesting a non-existent catalog name should error", func() {
					So(Show(ui, "notgonnafindit"), ShouldErrorWith, ErrDataNotFound)
				})
				Convey("`hitch show <catalog>:<release>` should only show that release", func() {
					output, err := grabOutput(func(ui UI) error {
						return Show(ui, "cn:v0.1")
					})
					So(err, ShouldErrorWith, nil)
					So(output, ShouldContainSubstring, `"v0.1"`)            // releases contain their own name
					So(output, ShouldContainSubstring, `"tar:asdfasdf"`)    // wareIDs in the first release should appear
					So(strings.Count(output, `"metadata"`), ShouldEqual, 1) // keyword "metadata" should appear once, because it's just one release entry
					So(output, ShouldNotContainSubstring, `"cn"`)           // releases don't repeat the name of the catalog that contains them
					So(output, ShouldNotContainSubstring, `"v0.2"`)         // the other releases names certainly shouldn't appear
					So(output, ShouldNotContainSubstring, `"tar:qwerqwer"`) // wareIDs in the other releases certainly shouldn't should appear
				})
				Convey("`hitch show <catalog>:<release>` requesting a non-existent catalog name should error", func() {
					So(Show(ui, "notgonnafindit:wompwomp"), ShouldErrorWith, ErrDataNotFound)
				})
				Convey("`hitch show <catalog>:<release>` requesting a non-existent release name should error", func() {
					So(Show(ui, "cn:wompwomp"), ShouldErrorWith, ErrDataNotFound)
				})
			})

		})
	})
}

func grabOutput(fn func(UI) error) (string, error) {
	var buf bytes.Buffer
	err := fn(UI{nil, &buf, &buf})
	return buf.String(), err
}
<commit_msg>core: test for hitch show of a specific item.<commit_after>package core

import (
	"bytes"
	"io/ioutil"
	"os"
	"strings"
	"testing"

	. "github.com/smartystreets/goconvey/convey"

	. "go.polydawn.net/hitch/lib/errcat"
	. "go.polydawn.net/hitch/lib/testutil"
)

func Test(t *testing.T) {
	ui := UI{
		nil,
		ioutil.Discard,
		ioutil.Discard,
	}
	Convey("Initialization / db-finding scenarios", t, func() {
		WithChdirTmpdir(func() {
			Convey("init happy path", func() {
				So(Init(ui), ShouldErrorWith, nil)
			})
			Convey("init repeated should fail", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(Init(ui), ShouldErrorWith, ErrInProgress)
			})
			Convey("init inside another db should fail", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(os.Mkdir("deeper", 0755), ShouldBeNil)
				So(os.Chdir("deeper"), ShouldBeNil)
				So(Init(ui), ShouldErrorWith, ErrInProgress)
			})
			Convey("catalog-create finds db in cwd", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
			})
			Convey("catalog-create finds db when deeper", func() {
				So(Init(ui), ShouldErrorWith, nil)
				So(os.Mkdir("deeper", 0755), ShouldBeNil)
				So(os.Chdir("deeper"), ShouldBeNil)
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
			})
		})
	})
	Convey("Release staging operations", t, func() {
		WithChdirTmpdir(func() {
			Must(Init(ui))

			Convey("starting a release to a nonexistent catalog should be rejected", func() {
				So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, ErrDataNotFound)
			})
			Convey("setting up a simple release with several items, happy path", func() {
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
				So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, nil)
				So(ReleaseAddItem(ui, "label-foo", "tar:asdfasdf"), ShouldErrorWith, nil)
				So(ReleaseAddItem(ui, "label-bar", "tar:asdfasdf"), ShouldErrorWith, nil)
				Convey("adding a clearly invalid wareID should be rejected", func() {
					So(ReleaseAddItem(ui, "label-bar", "not a ware id!"), ShouldErrorWith, ErrBadArgs)
				})
				// TODO : overwrite detection not implemented yet!
				//	Convey("overwriting an item should be rejected", func() {
				//		So(ReleaseAddItem(ui, "label-bar", "tar:asdfasdf"), ShouldErrorWith, ErrOverwrite)
				//	})
				Convey("...should be able to commit!", func() {
					So(ReleaseCommit(ui), ShouldErrorWith, nil)
					Convey("starting a new release with a new name should fly", func() {
						So(ReleaseStart(ui, "cn", "rn-v200"), ShouldErrorWith, nil)
					})
					Convey("starting a new release with the same name should be rejected", func() {
						So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, ErrNameCollision)
					})
				})
			})
			Convey("starting a release twice should be rejected", func() {
				So(CatalogCreate(ui, "cn"), ShouldErrorWith, nil)
				So(ReleaseStart(ui, "cn", "rn"), ShouldErrorWith, nil)
				So(ReleaseStart(ui, "cn", "zw"), ShouldErrorWith, ErrInProgress)
				// TODO : `hitch release reset` not implemented yet!
				//	Convey("reseting it should allow starting over", func() {
				//		So(ReleaseReset(ui), ShouldErrorWith, nil)
				//		So(ReleaseStart(ui, "cn", "zw"), ShouldErrorWith, nil)
				//	})
			})
		})
	})
	Convey("Show command operations", t, func() {
		WithChdirTmpdir(func() {
			Must(Init(ui))

			Convey("given a sizable catalog", func() {
				Must(CatalogCreate(ui, "cn"))
				Must(ReleaseStart(ui, "cn", "v0.1"))
				Must(ReleaseAddItem(ui, "label-foo", "tar:asdfasdf"))
				Must(ReleaseAddItem(ui, "label-bar", "tar:asdfqwer"))
				Must(ReleaseCommit(ui))
				Must(ReleaseStart(ui, "cn", "v0.2"))
				Must(ReleaseAddItem(ui, "label-foo", "tar:qwerasdf"))
				Must(ReleaseAddItem(ui, "label-bar", "tar:qwerqwer"))
				Must(ReleaseAddItem(ui, "label-qux", "tar:qwerzxcv"))
				Must(ReleaseCommit(ui))

				Convey("`hitch show <catalog>` should say a *lot*", func() {
					output, err := grabOutput(func(ui UI) error {
						return Show(ui, "cn")
					})
					So(err, ShouldErrorWith, nil)
					So(output, ShouldContainSubstring, `"cn"`)              // catalogs contain their own name
					So(output, ShouldContainSubstring, `"v0.1"`)            // both release names should appear
					So(output, ShouldContainSubstring, `"v0.2"`)            // both release names should appear
					So(output, ShouldContainSubstring, `"tar:asdfasdf"`)    // wareIDs in the first release should appear -- showing a catalog is *loud*!
					So(output, ShouldContainSubstring, `"tar:qwerqwer"`)    // wareIDs in the second release should appear -- showing a catalog is *loud*!
					So(strings.Count(output, `"metadata"`), ShouldEqual, 2) // keyword "metadata" should appear once per release entry
				})
				Convey("`hitch show <catalog>` requesting a non-existent catalog name should error", func() {
					So(Show(ui, "notgonnafindit"), ShouldErrorWith, ErrDataNotFound)
				})
				Convey("`hitch show <catalog>:<release>` should only show that release", func() {
					output, err := grabOutput(func(ui UI) error {
						return Show(ui, "cn:v0.1")
					})
					So(err, ShouldErrorWith, nil)
					So(output, ShouldContainSubstring, `"v0.1"`)            // releases contain their own name
					So(output, ShouldContainSubstring, `"tar:asdfasdf"`)    // wareIDs in the first release should appear
					So(strings.Count(output, `"metadata"`), ShouldEqual, 1) // keyword "metadata" should appear once, because it's just one release entry
					So(output, ShouldNotContainSubstring, `"cn"`)           // releases don't repeat the name of the catalog that contains them
					So(output, ShouldNotContainSubstring, `"v0.2"`)         // the other releases names certainly shouldn't appear
					So(output, ShouldNotContainSubstring, `"tar:qwerqwer"`) // wareIDs in the other releases certainly shouldn't should appear
				})
				Convey("`hitch show <catalog>:<release>` requesting a non-existent catalog name should error", func() {
					So(Show(ui, "notgonnafindit:wompwomp"), ShouldErrorWith, ErrDataNotFound)
				})
				Convey("`hitch show <catalog>:<release>` requesting a non-existent release name should error", func() {
					So(Show(ui, "cn:wompwomp"), ShouldErrorWith, ErrDataNotFound)
				})
				Convey("`hitch show <catalog>:<release>:<item>` should only show that item", func() {
					output, err := grabOutput(func(ui UI) error {
						return Show(ui, "cn:v0.1:label-foo")
					})
					So(err, ShouldErrorWith, nil)
					So(output, ShouldEqual, `"tar:asdfasdf"`+"\n") // FIXME except we probably DO NOT want those quotes here...
				})
				Convey("`hitch show <catalog>:<release>:<item>` requesting a non-existent item name should error", func() {
					So(Show(ui, "cn:v0.1:notathing"), ShouldErrorWith, ErrDataNotFound)
				})
			})

		})
	})
}

func grabOutput(fn func(UI) error) (string, error) {
	var buf bytes.Buffer
	err := fn(UI{nil, &buf, &buf})
	return buf.String(), err
}
<|endoftext|>


<commit_before>package dagger

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/nsaje/dagger/structs"
	"github.com/twinj/uuid"
)

type HttpAPI struct {
	dispatcher  *Dispatcher
	subscribers *httpSubscribers
}

func NewHttpAPI(receiver *Receiver, dispatcher *Dispatcher) HttpAPI {
	return HttpAPI{
		dispatcher,
		&httpSubscribers{
			receiver: receiver,
			subs:     make(map[string]map[chan *structs.Tuple]struct{}),
			lock:     &sync.RWMutex{},
		},
	}
}

func (api HttpAPI) Serve() {
	http.HandleFunc("/submit", api.submitTuple)
	http.HandleFunc("/listen", api.listen)

	log.Fatal(http.ListenAndServe(":46632", nil))
}

func (api HttpAPI) submitTuple(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var t structs.Tuple
	err := decoder.Decode(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error parsing JSON: %s", err), 400)
	}

	err = validate(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Tuple validation error: %s", err), 400)
	}

	t.LWM = time.Now()
	t.ID = uuid.NewV4().String()
	err = api.dispatcher.ProcessTuple(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error dispatching tuple: %s", err), 500)
	}
}

func validate(t *structs.Tuple) error {
	if len(t.StreamID) == 0 {
		return errors.New("'stream_id' should not be empty")
	}
	if strings.ContainsAny(t.StreamID, "()") {
		return errors.New("'stream_id' should not contain parentheses")
	}

	if t.Timestamp.IsZero() {
		return errors.New("'timestamp' is mandatory")
	}

	return nil
}

func (api HttpAPI) listen(w http.ResponseWriter, r *http.Request) {
	topicGlob := r.FormValue("s")
	if len(topicGlob) == 0 {
		http.Error(w, "stream id missing in URL", 400)
	}
	ch := make(chan *structs.Tuple)
	disconnected := w.(http.CloseNotifier).CloseNotify()

	api.subscribers.SubscribeTo(topicGlob, ch)
	defer api.subscribers.UnsubscribeFrom(topicGlob, ch)
	for {
		select {
		case t := <-ch:
			json, _ := json.Marshal(t) // FIXME: error?
			w.Write(json)
			w.Write([]byte("\n"))
			w.(http.Flusher).Flush()
		case <-disconnected:
			return
		}
	}
}

type httpSubscribers struct {
	receiver *Receiver
	subs     map[string]map[chan *structs.Tuple]struct{}
	lock     *sync.RWMutex
}

func (hs *httpSubscribers) SubscribeTo(streamID string, ch chan *structs.Tuple) {
	hs.lock.Lock()
	defer hs.lock.Unlock()
	subscribersSet := hs.subs[streamID]
	if subscribersSet == nil {
		subscribersSet = make(map[chan *structs.Tuple]struct{})
		hs.receiver.SubscribeTo(streamID, hs)
	}
	subscribersSet[ch] = struct{}{}
	hs.subs[streamID] = subscribersSet
}

func (hs *httpSubscribers) UnsubscribeFrom(streamID string, ch chan *structs.Tuple) {
	hs.lock.Lock()
	defer hs.lock.Unlock()
	delete(hs.subs[streamID], ch)
	if len(hs.subs[streamID]) == 0 {
		hs.receiver.UnsubscribeFrom(streamID, hs)
	}
}

func (hs *httpSubscribers) ProcessTuple(t *structs.Tuple) error {
	hs.lock.RLock()
	defer hs.lock.RUnlock()
	for ch := range hs.subs[t.StreamID] {
		ch <- t
	}
	return nil
}
<commit_msg>added HTTP API streaming subscription<commit_after>package dagger

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/nsaje/dagger/structs"
	"github.com/twinj/uuid"
)

type HttpAPI struct {
	dispatcher  *Dispatcher
	subscribers *httpSubscribers
}

func NewHttpAPI(receiver *Receiver, dispatcher *Dispatcher) HttpAPI {
	return HttpAPI{
		dispatcher,
		&httpSubscribers{
			receiver: receiver,
			subs:     make(map[string]map[chan *structs.Tuple]struct{}),
			lock:     &sync.RWMutex{},
		},
	}
}

func (api HttpAPI) Serve() {
	http.HandleFunc("/submit", api.submit)
	http.HandleFunc("/submit_raw", api.submitRaw)
	http.HandleFunc("/listen", api.listen)

	log.Fatal(http.ListenAndServe(":46632", nil))
}

func (api HttpAPI) submit(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var t structs.Tuple
	err := decoder.Decode(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error parsing JSON: %s", err), 400)
		return
	}

	err = validate(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Tuple validation error: %s", err), 400)
		return
	}

	t.LWM = time.Now() // FIXME: think this through
	t.ID = uuid.NewV4().String()
	err = api.dispatcher.ProcessTuple(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error dispatching tuple: %s", err), 500)
	}
}

func validate(t *structs.Tuple) error {
	if len(t.StreamID) == 0 {
		return errors.New("'stream_id' should not be empty")
	}
	if strings.ContainsAny(t.StreamID, "()") {
		return errors.New("'stream_id' should not contain parentheses")
	}

	if t.Timestamp.IsZero() {
		return errors.New("'timestamp' is mandatory")
	}

	return nil
}

func (api HttpAPI) submitRaw(w http.ResponseWriter, r *http.Request) {
	streamID := r.FormValue("s")
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error reading POST body: %s", err), 500)
		return
	}
	t := structs.Tuple{
		StreamID:  streamID,
		ID:        uuid.NewV4().String(),
		Timestamp: time.Now(),
		LWM:       time.Now(),
		Data:      string(data),
	}
	err = api.dispatcher.ProcessTuple(&t)
	if err != nil {
		http.Error(w, fmt.Sprintf("Error dispatching tuple: %s", err), 500)
	}
}

func (api HttpAPI) listen(w http.ResponseWriter, r *http.Request) {
	topicGlob := r.FormValue("s")
	if len(topicGlob) == 0 {
		http.Error(w, "stream id missing in URL", 400)
		return
	}
	ch := make(chan *structs.Tuple)
	disconnected := w.(http.CloseNotifier).CloseNotify()

	api.subscribers.SubscribeTo(topicGlob, ch)
	defer api.subscribers.UnsubscribeFrom(topicGlob, ch)
	for {
		select {
		case t := <-ch:
			json, _ := json.Marshal(t) // FIXME: error?
			w.Write(json)
			w.Write([]byte("\n"))
			w.(http.Flusher).Flush()
		case <-disconnected:
			return
		}
	}
}

type httpSubscribers struct {
	receiver *Receiver
	subs     map[string]map[chan *structs.Tuple]struct{}
	lock     *sync.RWMutex
}

func (hs *httpSubscribers) SubscribeTo(streamID string, ch chan *structs.Tuple) {
	hs.lock.Lock()
	defer hs.lock.Unlock()
	subscribersSet := hs.subs[streamID]
	if subscribersSet == nil {
		subscribersSet = make(map[chan *structs.Tuple]struct{})
		hs.receiver.SubscribeTo(streamID, hs)
	}
	subscribersSet[ch] = struct{}{}
	hs.subs[streamID] = subscribersSet
}

func (hs *httpSubscribers) UnsubscribeFrom(streamID string, ch chan *structs.Tuple) {
	hs.lock.Lock()
	defer hs.lock.Unlock()
	delete(hs.subs[streamID], ch)
	if len(hs.subs[streamID]) == 0 {
		hs.receiver.UnsubscribeFrom(streamID, hs)
	}
}

func (hs *httpSubscribers) ProcessTuple(t *structs.Tuple) error {
	hs.lock.RLock()
	defer hs.lock.RUnlock()
	for ch := range hs.subs[t.StreamID] {
		ch <- t
	}
	return nil
}
<|endoftext|>


<commit_before>package device

import (
	"errors"
	"fmt"
	"net"
	"strconv"

	"github.com/xshellinc/iotit/device/config"
	"github.com/xshellinc/tools/constants"
	"github.com/xshellinc/tools/dialogs"
	"github.com/xshellinc/tools/lib/help"
	"github.com/xshellinc/tools/lib/ssh_helper"
)

const (
	raspMount = "p2"
)

// raspberryPi device
type raspberryPi struct {
	*sdFlasher
}

// Configure overrides sdFlasher Configure() method with custom config
func (d *raspberryPi) Configure() error {
	job := help.NewBackgroundJob()
	c := config.NewDefault(d.conf.SSH)

	*(c.GetConfigFn(config.Interface)) = *config.NewCallbackFn(configInterface, saveInterface)
	c.AddConfigFn(config.NewCallbackFn(nil, setupSSH))

	go func() {
		defer job.Close()

		if err := d.MountImg(raspMount); err != nil {
			job.Error(err)
		}
	}()

	// setup while background process mounting img
	if err := c.Setup(); err != nil {
		return err
	}

	if err := help.WaitJobAndSpin("waiting", job); err != nil {
		return err
	}

	// write configs that were setup above
	if err := c.Write(); err != nil {
		return err
	}

	if err := d.UnmountImg(); err != nil {
		return err
	}
	if err := d.Flash(); err != nil {
		return err
	}

	return d.Done()
}

// interfaceConfig is a value for raspberryPi for dhcpcd.conf
var interfaceConfig = `

interface %s
	noipv6rs
	static ip_address=%s
	static routers=%s
	static domain_name_servers=%s
`

// configInterface is a custom configInterface method uses interfaceConfig var
func configInterface(storage map[string]interface{}) error {
	device := []string{"eth0", "wlan0"}
	i := config.Interfaces{
		Address: "192.168.0.254",
		Netmask: "255.255.255.0",
		Gateway: "192.168.0.1",
		DNS:     "192.168.0.1",
	}

	if dialogs.YesNoDialog("Would you like to assign static IP address for your device?") {
		fmt.Println("[+] Available network interface: ")
		num := dialogs.SelectOneDialog("Please select a network interface:", device)
		fmt.Println("[+] ********NOTE: ADJUST THESE VALUES ACCORDING TO YOUR LOCAL NETWORK CONFIGURATION********")

		for {
			fmt.Printf("[+] Current values are:\n \t[+] Address:%s\n\t[+] Gateway:%s\n\t[+] Netmask:%s\n\t[+] DNS:%s\n",
				i.Address, i.Gateway, i.Netmask, i.DNS)

			if dialogs.YesNoDialog("Change values?") {
				config.SetInterfaces(&i)

				mask, _ := net.IPMask(net.ParseIP(i.Netmask).To4()).Size()

				storage[config.GetConstLiteral(config.Interface)] = fmt.Sprintf(interfaceConfig, device[num], i.Address+"/"+strconv.Itoa(mask), i.Gateway, i.DNS)

				switch device[num] {
				case "eth0":
					fmt.Println("[+]  Ethernet interface configuration was updated")
				case "wlan0":
					fmt.Println("[+]  wifi interface configuration was updated")
				}
			} else {
				break
			}
		}
	}

	return nil
}

// saveInterface is a custom method and it saves Interfaces value into /etc/dhcpcd.conf
func saveInterface(storage map[string]interface{}) error {

	if _, ok := storage[config.GetConstLiteral(config.Interface)]; !ok {
		return nil
	}

	ssh, ok := storage["ssh"].(ssh_helper.Util)
	if !ok {
		return errors.New("Cannot get ssh config")
	}

	fp := help.AddPathSuffix("unix", constants.MountDir, constants.ISAAX_CONF_DIR, "dhcpcd.conf")
	command := fmt.Sprintf(`echo "%s" >> %s`, storage[config.GetConstLiteral(config.Interface)], fp)

	_, eut, err := ssh.Run(command)
	if err != nil {
		return errors.New(err.Error() + ":" + eut)
	}

	return nil
}

func setupSSH(storage map[string]interface{}) error {
	ssh, ok := storage["ssh"].(ssh_helper.Util)
	if !ok {
		return errors.New("Cannot get ssh config")
	}

	f1 := help.AddPathSuffix("unix", constants.MountDir, constants.ISAAX_CONF_DIR, "rc2.d/K*ssh")
	f2 := help.AddPathSuffix("unix", constants.MountDir, constants.ISAAX_CONF_DIR, "rc2.d/S02ssh")

	command := fmt.Sprintf(`mv %s %s`, f1, f2)

	_, eut, err := ssh.Run(command)
	if err != nil {
		return errors.New(err.Error() + ":" + eut)
	}

	return nil
}
<commit_msg>Add ability to mount raspi boot partition and enable SSH<commit_after>package device

import (
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/xshellinc/iotit/device/config"
	"github.com/xshellinc/tools/constants"
	"github.com/xshellinc/tools/dialogs"
	"github.com/xshellinc/tools/lib/help"
	"github.com/xshellinc/tools/lib/ssh_helper"
)

const (
	raspiBoot = "p1"
	raspiMain = "p2"
	bootMount = "/tmp/isaax-boot/"
)

// raspberryPi device
type raspberryPi struct {
	*sdFlasher
}

// Configure overrides sdFlasher Configure() method with custom config
func (d *raspberryPi) Configure() error {
	log.Println("Configure...")
	job := help.NewBackgroundJob()
	c := config.NewDefault(d.conf.SSH)

	*(c.GetConfigFn(config.Interface)) = *config.NewCallbackFn(configInterface, saveInterface)
	c.AddConfigFn(config.NewCallbackFn(setupSSH, nil))

	go func() {
		defer job.Close()

		if err := d.MountImg(raspiMain); err != nil {
			job.Error(err)
			return
		}
		if err := d.MountBoot(); err != nil {
			job.Error(err)
		}
	}()

	// setup while background process mounting img
	if err := c.Setup(); err != nil {
		return err
	}

	if err := help.WaitJobAndSpin("waiting", job); err != nil {
		return err
	}

	// write configs that were setup above
	if err := c.Write(); err != nil {
		return err
	}

	if err := d.UnmountImg(); err != nil {
		return err
	}
	if err := d.Flash(); err != nil {
		return err
	}

	return d.Done()
}

// interfaceConfig is a value for raspberryPi for dhcpcd.conf
var interfaceConfig = `

interface %s
	noipv6rs
	static ip_address=%s
	static routers=%s
	static domain_name_servers=%s
`

// configInterface is a custom configInterface method uses interfaceConfig var
func configInterface(storage map[string]interface{}) error {
	device := []string{"eth0", "wlan0"}
	i := config.Interfaces{
		Address: "192.168.0.254",
		Netmask: "255.255.255.0",
		Gateway: "192.168.0.1",
		DNS:     "192.168.0.1",
	}

	if dialogs.YesNoDialog("Would you like to assign static IP address for your device?") {
		fmt.Println("[+] Available network interface: ")
		num := dialogs.SelectOneDialog("Please select a network interface:", device)
		fmt.Println("[+] ********NOTE: ADJUST THESE VALUES ACCORDING TO YOUR LOCAL NETWORK CONFIGURATION********")

		for {
			fmt.Printf("[+] Current values are:\n \t[+] Address:%s\n\t[+] Gateway:%s\n\t[+] Netmask:%s\n\t[+] DNS:%s\n",
				i.Address, i.Gateway, i.Netmask, i.DNS)

			if dialogs.YesNoDialog("Change values?") {
				config.SetInterfaces(&i)

				mask, _ := net.IPMask(net.ParseIP(i.Netmask).To4()).Size()

				storage[config.GetConstLiteral(config.Interface)] = fmt.Sprintf(interfaceConfig, device[num], i.Address+"/"+strconv.Itoa(mask), i.Gateway, i.DNS)

				switch device[num] {
				case "eth0":
					fmt.Println("[+]  Ethernet interface configuration was updated")
				case "wlan0":
					fmt.Println("[+]  wifi interface configuration was updated")
				}
			} else {
				break
			}
		}
	}

	return nil
}

// saveInterface is a custom method and it saves Interfaces value into /etc/dhcpcd.conf
func saveInterface(storage map[string]interface{}) error {

	if _, ok := storage[config.GetConstLiteral(config.Interface)]; !ok {
		return nil
	}

	ssh, ok := storage["ssh"].(ssh_helper.Util)
	if !ok {
		return errors.New("Cannot get ssh config")
	}

	fp := help.AddPathSuffix("unix", constants.MountDir, constants.ISAAX_CONF_DIR, "dhcpcd.conf")
	command := fmt.Sprintf(`echo "%s" >> %s`, storage[config.GetConstLiteral(config.Interface)], fp)

	_, eut, err := ssh.Run(command)
	if err != nil {
		return errors.New(err.Error() + ":" + eut)
	}

	return nil
}

// setupSSH is enabling ssh server on pi
func setupSSH(storage map[string]interface{}) error {
	if dialogs.YesNoDialog("Would you like to enable SSH server?") {
		ssh, ok := storage["ssh"].(ssh_helper.Util)
		if !ok {
			return errors.New("Cannot get ssh config")
		}
		command := fmt.Sprintf("touch %sssh", bootMount)
		if _, eut, err := ssh.Run(command); err != nil {
			return errors.New(err.Error() + ":" + eut)
		}
	}
	return nil
}

// MountImg is a method to attach image to loop and mount it
func (d *raspberryPi) MountBoot() error {
	log.Debug("Mounting boot partition")
	//check if image is attached?
	// command := fmt.Sprintf("losetup -f -P %s", help.AddPathSuffix("unix", constants.TMP_DIR, d.img))
	// log.WithField("cmd", command).Debug("Attaching image loop device")
	// if err := d.exec(command); err != nil {
	// 	return err
	// }

	log.Debug("Creating tmp folder")
	if err := d.exec(fmt.Sprintf("mkdir -p %s", bootMount)); err != nil {
		return err
	}

	command := fmt.Sprintf("mount -o rw /dev/loop0%s %s", raspiBoot, bootMount)
	log.WithField("cmd", command).Debug("Mounting tmp folder")
	if err := d.exec(command); err != nil {
		return err
	}
	return nil
}

// UnmountImg is a method to unlink image folder and detach image from the loop
func (d *raspberryPi) UnmountBoot() error {
	log.Debug("Unlinking boot folder")
	command := fmt.Sprintf("umount %s", bootMount)
	if err := d.exec(command); err != nil {
		return err
	}

	log.Debug("Detaching and image")
	command = "losetup -D" // -D detaches all loop devices
	if err := d.exec(command); err != nil {
		return err
	}
	return nil
}

func (d *raspberryPi) exec(command string) error {
	if out, eut, err := d.conf.SSH.Run(command); err != nil {
		log.Error("[-] Error executing: ", command, eut)
		return err
	} else if strings.TrimSpace(out) != "" {
		log.Debug(strings.TrimSpace(out))
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package storage

import (
	"strings"

	"github.com/TheThingsNetwork/ttn/utils/errors"
	redis "gopkg.in/redis.v5"
)

// VersionKey indicates the data (schema) version
const VersionKey = "_version"

type hasDBVersion interface {
	DBVersion() string
}

// MigrateFunction migrates data from its old version to the latest
type MigrateFunction func(client *redis.Client, key string, input map[string]string) (version string, output map[string]string, err error)

// AddMigration adds a data migration for a version
func (s *RedisMapStore) AddMigration(version string, migrate MigrateFunction) {
	s.migrations[version] = migrate
}

// Migrate all documents matching the selector
func (s *RedisMapStore) Migrate(selector string) error {
	if selector == "" {
		selector = "*"
	}
	if !strings.HasPrefix(selector, s.prefix) {
		selector = s.prefix + selector
	}

	var cursor uint64
	for {
		keys, next, err := s.client.Scan(cursor, selector, 0).Result()
		if err != nil {
			return err
		}

		for _, key := range keys {
			// Get migrates the item
			_, err := s.Get(key)

			// NotFound if item was deleted since Scan started
			if errors.GetErrType(err) == errors.NotFound {
				continue
			}

			// TxFailedError is returned if the item was changed by a concurrent process.
			// If it was a parallel migration, we don't have to do anything
			// Otherwise, this item will be migrated with the next Get
			if err == redis.TxFailedErr {
				continue
			}

			// Any other error should be returned
			if err != nil {
				return err
			}
		}

		cursor = next
		if cursor == 0 {
			break
		}
	}

	return nil
}

func (s *RedisMapStore) migrate(key string, obj map[string]string) (map[string]string, error) {
	var err error
	version, _ := obj[VersionKey]
	migration, ok := s.migrations[version]

	if !ok {
		return obj, nil
	}

	var oldFields []string
	for k := range obj {
		oldFields = append(oldFields, k)
	}

	err = s.client.Watch(func(tx *redis.Tx) error { // Make sure objects are not changed while we're migrating them
		// As long as there are migrations to do
		for ok {
			version, obj, err = migration(s.client, key, obj)
			if err != nil {
				return err
			}
			obj[VersionKey] = version
			migration, ok = s.migrations[version]
		}

		// Collect deleted fields
		var deletedFields []string
		for _, k := range oldFields {
			if _, ok := obj[k]; !ok {
				deletedFields = append(deletedFields, k)
			}
		}

		// Commit the new version
		_, err := tx.Pipelined(func(pipe *redis.Pipeline) error {
			pipe.HMSet(key, obj)
			pipe.HDel(key, deletedFields...)
			return nil
		})

		return err
	}, key)

	return obj, err
}
<commit_msg>Only run HDel if there are fields to delete<commit_after>// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package storage

import (
	"strings"

	"github.com/TheThingsNetwork/ttn/utils/errors"
	redis "gopkg.in/redis.v5"
)

// VersionKey indicates the data (schema) version
const VersionKey = "_version"

type hasDBVersion interface {
	DBVersion() string
}

// MigrateFunction migrates data from its old version to the latest
type MigrateFunction func(client *redis.Client, key string, input map[string]string) (version string, output map[string]string, err error)

// AddMigration adds a data migration for a version
func (s *RedisMapStore) AddMigration(version string, migrate MigrateFunction) {
	s.migrations[version] = migrate
}

// Migrate all documents matching the selector
func (s *RedisMapStore) Migrate(selector string) error {
	if selector == "" {
		selector = "*"
	}
	if !strings.HasPrefix(selector, s.prefix) {
		selector = s.prefix + selector
	}

	var cursor uint64
	for {
		keys, next, err := s.client.Scan(cursor, selector, 0).Result()
		if err != nil {
			return err
		}

		for _, key := range keys {
			// Get migrates the item
			_, err := s.Get(key)

			// NotFound if item was deleted since Scan started
			if errors.GetErrType(err) == errors.NotFound {
				continue
			}

			// TxFailedError is returned if the item was changed by a concurrent process.
			// If it was a parallel migration, we don't have to do anything
			// Otherwise, this item will be migrated with the next Get
			if err == redis.TxFailedErr {
				continue
			}

			// Any other error should be returned
			if err != nil {
				return err
			}
		}

		cursor = next
		if cursor == 0 {
			break
		}
	}

	return nil
}

func (s *RedisMapStore) migrate(key string, obj map[string]string) (map[string]string, error) {
	var err error
	version, _ := obj[VersionKey]
	migration, ok := s.migrations[version]

	if !ok {
		return obj, nil
	}

	var oldFields []string
	for k := range obj {
		oldFields = append(oldFields, k)
	}

	err = s.client.Watch(func(tx *redis.Tx) error { // Make sure objects are not changed while we're migrating them
		// As long as there are migrations to do
		for ok {
			version, obj, err = migration(s.client, key, obj)
			if err != nil {
				return err
			}
			obj[VersionKey] = version
			migration, ok = s.migrations[version]
		}

		// Collect deleted fields
		var deletedFields []string
		for _, k := range oldFields {
			if _, ok := obj[k]; !ok {
				deletedFields = append(deletedFields, k)
			}
		}

		// Commit the new version
		_, err := tx.Pipelined(func(pipe *redis.Pipeline) error {
			pipe.HMSet(key, obj)
			if len(deletedFields) > 0 {
				pipe.HDel(key, deletedFields...)
			}
			return nil
		})

		return err
	}, key)

	return obj, err
}
<|endoftext|>


<commit_before>/*
Copyright 2022 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package dialer implements a gRPC dialer over a tunneled connection.
package dialer

import (
	"context"
	"fmt"
	"net"

	"github.com/openconfig/grpctunnel/tunnel/tunnel"
	"google.golang.org/grpc"
)

// ClientDialer performs dialing to targets behind a given tunnel connection.
type ClientDialer struct {
	tc *tunnel.Client
}

// ServerDialer performs dialing to collector via a given tunnel connection.
type ServerDialer struct {
	ts *tunnel.Server
}

// FromClient creates a new target dialer with an existing tunnel client
// connection.
// Sample code with error handling elided:
//
// conn, err := grpc.DialContext(ctx, tunnelAddress)
// client := tpb.NewTunnelClient(conn)
// tc := tunnel.NewClient(client, tunnel.ClientConfig{}, nil)
// d := dialer.FromClient(tc)
func FromClient(tc *tunnel.Client) (*ClientDialer, error) {
	if tc == nil {
		return nil, fmt.Errorf("tunnel client connection is nil")
	}
	return &ClientDialer{tc: tc}, nil
}

// FromServer creates a new collector dialer with an existing tunnel server
// connection.
// Sample code with error handling elided:
//
// conn, err := grpc.DialContext(ctx, tunnelAddress)
// ts := tunnel.NewServer(tunnel.ServerConfig{})
// d := dialer.FromServer(ts)
func FromServer(ts *tunnel.Server) (*ServerDialer, error) {
	if ts == nil {
		return nil, fmt.Errorf("tunnel server connection is nil")
	}
	return &ServerDialer{ts: ts}, nil
}

// DialContext establishes a grpc.Conn to a remote tunnel client via the
// attached tunnel server and returns an error if the connection is not
// established.
//
// The dialer can be used to create connections to multiple targets behind the
// same tunnel server used to instantiate the dialer.
//
// conn, err := d.DialContext(ctx, "target1", "target-type1", opts1)
// conn, err := d.DialContext(ctx, "target2", "target-type2", opts2)
func (cd *ClientDialer) DialContext(ctx context.Context, target, targetType string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
	withContextDialer := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
		return tunnel.ClientConn(ctx, cd.tc, &tunnel.Target{ID: target, Type: targetType})
	})
	opts = append(opts, withContextDialer)
	return grpc.DialContext(ctx, target, opts...)
}

// DialContext establishes a grpc.Conn to the attached tunnel server and
// returns an error if the connection is not established.
func (sd *ServerDialer) DialContext(ctx context.Context, target string, targetType string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
	withContextDialer := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
		return tunnel.ServerConn(ctx, sd.ts, &tunnel.Target{ID: target, Type: targetType})
	})
	opts = append(opts, withContextDialer)
	return grpc.DialContext(ctx, target, opts...)
}
<commit_msg>addressing comments<commit_after>/*
Copyright 2022 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package dialer implements a gRPC dialer over a tunneled connection.
package dialer

import (
	"context"
	"fmt"
	"net"

	"github.com/openconfig/grpctunnel/tunnel"
	"google.golang.org/grpc"
)

// ClientDialer performs dialing to targets behind a given tunnel connection.
type ClientDialer struct {
	tc *tunnel.Client
}

// ServerDialer performs dialing to collector via a given tunnel connection.
type ServerDialer struct {
	ts *tunnel.Server
}

// FromClient creates a new target dialer with an existing tunnel client
// connection.
// Sample code with error handling elided:
//
// conn, err := grpc.DialContext(ctx, tunnelAddress)
// client := tpb.NewTunnelClient(conn)
// tc := tunnel.NewClient(client, tunnel.ClientConfig{}, nil)
// d := dialer.FromClient(tc)
func FromClient(tc *tunnel.Client) (*ClientDialer, error) {
	if tc == nil {
		return nil, fmt.Errorf("tunnel client connection is nil")
	}
	return &ClientDialer{tc: tc}, nil
}

// FromServer creates a new collector dialer with an existing tunnel server.
// Sample code with error handling elided:
//
// ts := tunnel.NewServer(tunnel.ServerConfig{})
// d := dialer.FromServer(ts)
func FromServer(ts *tunnel.Server) (*ServerDialer, error) {
	if ts == nil {
		return nil, fmt.Errorf("tunnel server connection is nil")
	}
	return &ServerDialer{ts: ts}, nil
}

// DialContext establishes a grpc.Conn to a remote tunnel client via the
// attached tunnel server and returns an error if the connection is not
// established.
//
// The dialer can be used to create connections to multiple targets behind the
// same tunnel server used to instantiate the dialer.
//
// conn, err := d.DialContext(ctx, "target1", "target-type1", opts1)
// conn, err := d.DialContext(ctx, "target2", "target-type2", opts2)
func (d *ClientDialer) DialContext(ctx context.Context, target, targetType string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
	withContextDialer := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
		return tunnel.ClientConn(ctx, d.tc, &tunnel.Target{ID: target, Type: targetType})
	})
	opts = append(opts, withContextDialer)
	return grpc.DialContext(ctx, target, opts...)
}

// DialContext establishes a grpc.Conn to the attached tunnel server and
// returns an error if the connection is not established.
func (d *ServerDialer) DialContext(ctx context.Context, target string, targetType string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
	withContextDialer := grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
		return tunnel.ServerConn(ctx, d.ts, &tunnel.Target{ID: target, Type: targetType})
	})
	opts = append(opts, withContextDialer)
	return grpc.DialContext(ctx, target, opts...)
}
<|endoftext|>


<commit_before>package nessie

import (
	"crypto/tls"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestDoRequest(t *testing.T) {
	// Test structure to be serialized.
	type payload struct {
		A int `json:"a"`
	}
	var tests = []struct {
		method       string
		resource     string
		sentPayload  payload
		wantPayload  string
		serverStatus int
		wantStatus   []int
		wantError    bool
	}{
		// All succeeding methods.
		{"GET", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		{"POST", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		{"DELETE", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		{"PUT", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		// Payload test.
		{"GET", "/test", payload{42}, "{\"a\":42}", http.StatusOK, []int{http.StatusOK}, false},
		// Expected failure.
		{"POST", "/test", payload{}, "{\"a\":0}", http.StatusInternalServerError, []int{http.StatusInternalServerError}, false},
		// Unexpected failure
		{"POST", "/test", payload{}, "{\"a\":0}", http.StatusInternalServerError, []int{http.StatusOK}, true},
	}
	for _, tt := range tests {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(tt.serverStatus)
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Errorf("could not read request body: %v", err)
				return
			}
			bodyStr := string(body)
			if bodyStr != tt.wantPayload {
				t.Errorf("unexpected payload, got=%s, want=%s", body, tt.wantPayload)
			}
		}))
		n := &nessusImpl{
			apiURL: ts.URL,
			client: &http.Client{
				Transport: &http.Transport{
					TLSClientConfig: &tls.Config{
						InsecureSkipVerify: true,
					},
				},
			},
		}
		n.SetVerbose(true)
		resp, err := n.doRequest(tt.method, tt.resource, tt.sentPayload, tt.wantStatus)
		if tt.wantError {
			if err == nil {
				t.Errorf("got no error, expected one (%+v)", tt)
			}
			continue
		}
		if err != nil {
			t.Errorf("error in doRequest: %v (%+v)", err, tt)
			continue
		}
		if resp.StatusCode != tt.serverStatus {
			t.Errorf("got status code=%d, wanted=%d", resp.StatusCode, tt.serverStatus)
		}
	}
}

func TestLogin(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Header().Set("Content-Type", "application/json")
		j, err := json.Marshal(&loginResp{Token: "some token"})
		if err != nil {
			t.Fatalf("cannot serialize login response: %v", err)
		}
		w.Write(j)
	}))
	defer server.Close()
	n, err := NewInsecureNessus(server.URL)
	if err != nil {
		t.Fatalf("cannot create nessus instance: %v", err)
	}

	if err := n.Login("username", "password"); err != nil {
		t.Fatalf("got error during login: %v", err)
	}
	if got, want := n.AuthCookie(), "some token"; got != want {
		t.Fatalf("wrong auth cookie, got=%q, want=%q", got, want)
	}
}

func TestMethods(t *testing.T) {
	var tests = []struct {
		resp       interface{}
		statusCode int
		call       func(n Nessus)
	}{
		{&Session{}, http.StatusOK, func(n Nessus) { n.Session() }},
		{&ServerProperties{}, http.StatusOK, func(n Nessus) { n.ServerProperties() }},
		{&ServerStatus{}, http.StatusOK, func(n Nessus) { n.ServerStatus() }},
		{&User{}, http.StatusOK, func(n Nessus) {
			n.CreateUser("username", "pass", UserTypeLocal, Permissions32, "name", "email@foo.com")
		}},
		{&listUsersResp{}, http.StatusOK, func(n Nessus) { n.ListUsers() }},
		{nil, http.StatusOK, func(n Nessus) { n.DeleteUser(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.SetUserPassword(42, "newpass") }},
		{&User{}, http.StatusOK, func(n Nessus) {
			n.EditUser(42, Permissions128, "newname", "newmain@goo.fom")
		}},
		{[]PluginFamily{}, http.StatusOK, func(n Nessus) { n.PluginFamilies() }},
		{&FamilyDetails{}, http.StatusOK, func(n Nessus) { n.FamilyDetails(42) }},
		{&PluginDetails{}, http.StatusOK, func(n Nessus) { n.PluginDetails(42) }},
		{[]Scanner{}, http.StatusOK, func(n Nessus) { n.Scanners() }},
		{&listPoliciesResp{}, http.StatusOK, func(n Nessus) { n.Policies() }},
		{&Scan{}, http.StatusOK, func(n Nessus) {
			n.NewScan("editorUUID", "settingsName", 42, 43, 44, LaunchDaily, []string{"target1", "target2"})
		}},
		{&ListScansResponse{}, http.StatusOK, func(n Nessus) { n.Scans() }},
		{[]Template{}, http.StatusOK, func(n Nessus) { n.ScanTemplates() }},
		{[]Template{}, http.StatusOK, func(n Nessus) { n.PolicyTemplates() }},
		{"id", http.StatusOK, func(n Nessus) { n.StartScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.PauseScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.ResumeScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.StopScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.DeleteScan(42) }},
		{&ScanDetailsResp{}, http.StatusOK, func(n Nessus) { n.ScanDetails(42) }},
		{[]TimeZone{}, http.StatusOK, func(n Nessus) { n.Timezones() }},
		{[]Folder{}, http.StatusOK, func(n Nessus) { n.Folders() }},
		{nil, http.StatusOK, func(n Nessus) { n.CreateFolder("name") }},
		{nil, http.StatusOK, func(n Nessus) { n.EditFolder(42, "newname") }},
		{nil, http.StatusOK, func(n Nessus) { n.DeleteFolder(42) }},
		{42, http.StatusOK, func(n Nessus) { n.ExportScan(42, ExportPDF) }},
		{true, http.StatusOK, func(n Nessus) { n.ExportFinished(42, 43) }},
		{[]byte("raw export"), http.StatusOK, func(n Nessus) { n.DownloadExport(42, 43) }},
		{[]Permission{}, http.StatusOK, func(n Nessus) { n.Permissions("scanner", 42) }},
	}
	for _, tt := range tests {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(tt.statusCode)
			if tt.resp != nil {
				j, err := json.Marshal(tt.resp)
				if err != nil {
					t.Fatalf("cannot serialize response: %v", err)
				}
				w.Write(j)
			}
		}))
		defer server.Close()
		n, err := NewInsecureNessus(server.URL)
		if err != nil {
			t.Fatalf("cannot create nessus instance: %v", err)
		}
		n.SetVerbose(true)
		tt.call(n)
	}
}
<commit_msg>Tests to verify peer by sha256 cert fingerprint.<commit_after>package nessie

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"io/ioutil"
	"math/big"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"
)

func TestDoRequest(t *testing.T) {
	// Test structure to be serialized.
	type payload struct {
		A int `json:"a"`
	}
	var tests = []struct {
		method       string
		resource     string
		sentPayload  payload
		wantPayload  string
		serverStatus int
		wantStatus   []int
		wantError    bool
	}{
		// All succeeding methods.
		{"GET", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		{"POST", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		{"DELETE", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		{"PUT", "/test", payload{}, "{\"a\":0}", http.StatusOK, []int{http.StatusOK}, false},
		// Payload test.
		{"GET", "/test", payload{42}, "{\"a\":42}", http.StatusOK, []int{http.StatusOK}, false},
		// Expected failure.
		{"POST", "/test", payload{}, "{\"a\":0}", http.StatusInternalServerError, []int{http.StatusInternalServerError}, false},
		// Unexpected failure
		{"POST", "/test", payload{}, "{\"a\":0}", http.StatusInternalServerError, []int{http.StatusOK}, true},
	}
	for _, tt := range tests {
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(tt.serverStatus)
			body, err := ioutil.ReadAll(r.Body)
			if err != nil {
				t.Errorf("could not read request body: %v", err)
				return
			}
			bodyStr := string(body)
			if bodyStr != tt.wantPayload {
				t.Errorf("unexpected payload, got=%s, want=%s", body, tt.wantPayload)
			}
		}))
		n := &nessusImpl{
			apiURL: ts.URL,
			client: &http.Client{
				Transport: &http.Transport{
					TLSClientConfig: &tls.Config{
						InsecureSkipVerify: true,
					},
				},
			},
		}
		n.SetVerbose(true)
		resp, err := n.doRequest(tt.method, tt.resource, tt.sentPayload, tt.wantStatus)
		if tt.wantError {
			if err == nil {
				t.Errorf("got no error, expected one (%+v)", tt)
			}
			continue
		}
		if err != nil {
			t.Errorf("error in doRequest: %v (%+v)", err, tt)
			continue
		}
		if resp.StatusCode != tt.serverStatus {
			t.Errorf("got status code=%d, wanted=%d", resp.StatusCode, tt.serverStatus)
		}
	}
}

func TestLogin(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Header().Set("Content-Type", "application/json")
		j, err := json.Marshal(&loginResp{Token: "some token"})
		if err != nil {
			t.Fatalf("cannot serialize login response: %v", err)
		}
		w.Write(j)
	}))
	defer server.Close()
	n, err := NewInsecureNessus(server.URL)
	if err != nil {
		t.Fatalf("cannot create nessus instance: %v", err)
	}

	if err := n.Login("username", "password"); err != nil {
		t.Fatalf("got error during login: %v", err)
	}
	if got, want := n.AuthCookie(), "some token"; got != want {
		t.Fatalf("wrong auth cookie, got=%q, want=%q", got, want)
	}
}

func TestMethods(t *testing.T) {
	var tests = []struct {
		resp       interface{}
		statusCode int
		call       func(n Nessus)
	}{
		{&Session{}, http.StatusOK, func(n Nessus) { n.Session() }},
		{&ServerProperties{}, http.StatusOK, func(n Nessus) { n.ServerProperties() }},
		{&ServerStatus{}, http.StatusOK, func(n Nessus) { n.ServerStatus() }},
		{&User{}, http.StatusOK, func(n Nessus) {
			n.CreateUser("username", "pass", UserTypeLocal, Permissions32, "name", "email@foo.com")
		}},
		{&listUsersResp{}, http.StatusOK, func(n Nessus) { n.ListUsers() }},
		{nil, http.StatusOK, func(n Nessus) { n.DeleteUser(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.SetUserPassword(42, "newpass") }},
		{&User{}, http.StatusOK, func(n Nessus) {
			n.EditUser(42, Permissions128, "newname", "newmain@goo.fom")
		}},
		{[]PluginFamily{}, http.StatusOK, func(n Nessus) { n.PluginFamilies() }},
		{&FamilyDetails{}, http.StatusOK, func(n Nessus) { n.FamilyDetails(42) }},
		{&PluginDetails{}, http.StatusOK, func(n Nessus) { n.PluginDetails(42) }},
		{[]Scanner{}, http.StatusOK, func(n Nessus) { n.Scanners() }},
		{&listPoliciesResp{}, http.StatusOK, func(n Nessus) { n.Policies() }},
		{&Scan{}, http.StatusOK, func(n Nessus) {
			n.NewScan("editorUUID", "settingsName", 42, 43, 44, LaunchDaily, []string{"target1", "target2"})
		}},
		{&ListScansResponse{}, http.StatusOK, func(n Nessus) { n.Scans() }},
		{[]Template{}, http.StatusOK, func(n Nessus) { n.ScanTemplates() }},
		{[]Template{}, http.StatusOK, func(n Nessus) { n.PolicyTemplates() }},
		{"id", http.StatusOK, func(n Nessus) { n.StartScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.PauseScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.ResumeScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.StopScan(42) }},
		{nil, http.StatusOK, func(n Nessus) { n.DeleteScan(42) }},
		{&ScanDetailsResp{}, http.StatusOK, func(n Nessus) { n.ScanDetails(42) }},
		{[]TimeZone{}, http.StatusOK, func(n Nessus) { n.Timezones() }},
		{[]Folder{}, http.StatusOK, func(n Nessus) { n.Folders() }},
		{nil, http.StatusOK, func(n Nessus) { n.CreateFolder("name") }},
		{nil, http.StatusOK, func(n Nessus) { n.EditFolder(42, "newname") }},
		{nil, http.StatusOK, func(n Nessus) { n.DeleteFolder(42) }},
		{42, http.StatusOK, func(n Nessus) { n.ExportScan(42, ExportPDF) }},
		{true, http.StatusOK, func(n Nessus) { n.ExportFinished(42, 43) }},
		{[]byte("raw export"), http.StatusOK, func(n Nessus) { n.DownloadExport(42, 43) }},
		{[]Permission{}, http.StatusOK, func(n Nessus) { n.Permissions("scanner", 42) }},
	}
	for _, tt := range tests {
		server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.WriteHeader(tt.statusCode)
			if tt.resp != nil {
				j, err := json.Marshal(tt.resp)
				if err != nil {
					t.Fatalf("cannot serialize response: %v", err)
				}
				w.Write(j)
			}
		}))
		defer server.Close()
		n, err := NewInsecureNessus(server.URL)
		if err != nil {
			t.Fatalf("cannot create nessus instance: %v", err)
		}
		n.SetVerbose(true)
		tt.call(n)
	}
}

func TestSha256Fingerprint(t *testing.T) {
	want := "AzuD2SQxVI4TQkkDwjWpkir1bdNNU8m3KzfPFYSJIT4="
	got := sha256Fingerprint([]byte("abc123!"))
	if got != want {
		t.Errorf("fingerprint calculation failed, got=%v, want=%v", got, want)
	}
}

func generateCert(validNotBefore time.Time, validNotAfter time.Time) (*x509.Certificate, tls.Certificate, error) {
	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, tls.Certificate{}, err
	}
	template := x509.Certificate{
		BasicConstraintsValid: true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		NotBefore:             validNotBefore,
		NotAfter:              validNotAfter,
		SerialNumber:          big.NewInt(1),
		Subject:               pkix.Name{Organization: []string{"Example Inc"}},
	}
	certDer, err := x509.CreateCertificate(rand.Reader, &template, &template, &privKey.PublicKey, privKey)
	if err != nil {
		return nil, tls.Certificate{}, err
	}
	certX509, err := x509.ParseCertificate(certDer)
	if err != nil {
		return nil, tls.Certificate{}, err
	}
	keypair, err := tls.X509KeyPair(
		pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDer}),
		pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privKey)}))
	return certX509, keypair, err
}

func TestCreateDialTLSFuncToVerifyFingerprint(t *testing.T) {
	var tests = []struct {
		fingerprintSuffix string
		validNotBefore    time.Time
		validNotAfter     time.Time
		wantError         bool
	}{
		{"", time.Now().Truncate(1 * time.Hour), time.Now().Add(1 * time.Hour), false},
		{"a", time.Now().Truncate(1 * time.Hour), time.Now().Add(1 * time.Hour), true},
		{"", time.Now().Add(1 * time.Hour), time.Now().Add(2 * time.Hour), true},
	}
	for _, tt := range tests {
		srvCertX509, srvKeypair, err := generateCert(tt.validNotBefore, tt.validNotAfter)
		if err != nil {
			t.Fatalf("failed to create x509 key pair: %v", err)
		}
		srvConfig := &tls.Config{Certificates: []tls.Certificate{srvKeypair}}
		srvListener, err := tls.Listen("tcp", "[::1]:0", srvConfig)
		if err != nil {
			t.Fatalf("cannot listen: %v", err)
			return
		}
		go http.Serve(srvListener, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}))
		cConfig := &tls.Config{
			InsecureSkipVerify: true,
		}
		wantFingerprint := sha256Fingerprint(srvCertX509.RawSubjectPublicKeyInfo) + tt.fingerprintSuffix
		client := &http.Client{
			Transport: &http.Transport{
				TLSClientConfig: cConfig,
				DialTLS:         createDialTLSFuncToVerifyFingerprint(wantFingerprint, cConfig),
			},
		}
		_, err = client.Get("https://" + srvListener.Addr().String())
		if tt.wantError {
			if err == nil {
				t.Errorf("got no error, expected one (%+v)", tt)
			}
			continue
		}
		if err != nil {
			t.Errorf("error during fingerprint verification: %v (%+v)", err, tt)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package lock

import (
	"fmt"
	"net/url"
	"time"

	"github.com/franela/goreq"
)

// LockTTL defines max time to live of a lock.
var LockTTL = time.Duration(20 * time.Second)

func init() {
	goreq.SetConnectTimeout(time.Second * 10)
}

// ConsulLock provides lock functionalities based on consul sessions.It implements
// The Lock interface.
type ConsulLock struct {
	consulAddr    string
	lockKeyPrefix string
	lockSession   string
}

// NewConsulLock creates a consul lock instance
func NewConsulLock() *ConsulLock {
	return &ConsulLock{
		consulAddr:    "http://localhost:8500",
		lockKeyPrefix: "platform/lock",
	}
}

// createSession creates a consul session
func (l *ConsulLock) createSession(ttl int) (string, error) {
	var ttlStr string
	if ttl > 0 {
		ttlStr = fmt.Sprintf("%ds", ttl)
	}
	item := url.Values{}
	item.Set("TTL", ttlStr)
	item.Set("Behaviour", "delete")
	resp, err := goreq.Request{
		Method:      "PUT",
		Uri:         l.consulAddr + "/v1/session/create",
		QueryString: item,
	}.Do()
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		b, _ := resp.Body.ToString()
		return "", fmt.Errorf(b)
	}

	var result map[string]string
	if err = resp.Body.FromJsonTo(&result); err != nil {
		return "", err
	}

	return result["ID"], nil
}

func (l *ConsulLock) acquire(key string) error {
	resp, err := goreq.Request{
		Method: "PUT",
		Uri:    l.consulAddr + "/v1/kv/" + fmt.Sprintf("%s?acquire=%s", l.lockKeyPrefix, l.lockSession),
	}.Do()
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		b, _ := resp.Body.ToString()
		return fmt.Errorf(b)
	}

	status, _ := resp.Body.ToString()
	fmt.Println("\n", status)
	if status == "false" {
		return fmt.Errorf("lock already acquired by another process")
	}

	return nil
}

// Acquire acquires a lock on a key. A time-to-live time is set
// on the lock to ensure the lock is invalidated after the time is passed.
func (l *ConsulLock) Acquire(key string) error {

	var err error

	l.lockSession, err = l.createSession(int(LockTTL.Seconds()))
	if err != nil {
		return fmt.Errorf("failed to get lock: %s", err)
	}

	err = l.acquire(key)

	return err
}

// Release invalidates the lock previously acquired
func (l *ConsulLock) Release() error {
	return nil
}
<commit_msg>trim any whitespace<commit_after>package lock

import (
	"fmt"
	"net/url"
	"time"

	"strings"

	"github.com/franela/goreq"
)

// LockTTL defines max time to live of a lock.
var LockTTL = time.Duration(20 * time.Second)

func init() {
	goreq.SetConnectTimeout(time.Second * 10)
}

// ConsulLock provides lock functionalities based on consul sessions.It implements
// The Lock interface.
type ConsulLock struct {
	consulAddr    string
	lockKeyPrefix string
	lockSession   string
}

// NewConsulLock creates a consul lock instance
func NewConsulLock() *ConsulLock {
	return &ConsulLock{
		consulAddr:    "http://localhost:8500",
		lockKeyPrefix: "platform/lock",
	}
}

// createSession creates a consul session
func (l *ConsulLock) createSession(ttl int) (string, error) {
	var ttlStr string
	if ttl > 0 {
		ttlStr = fmt.Sprintf("%ds", ttl)
	}
	item := url.Values{}
	item.Set("TTL", ttlStr)
	item.Set("Behaviour", "delete")
	resp, err := goreq.Request{
		Method:      "PUT",
		Uri:         l.consulAddr + "/v1/session/create",
		QueryString: item,
	}.Do()
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		b, _ := resp.Body.ToString()
		return "", fmt.Errorf(b)
	}

	var result map[string]string
	if err = resp.Body.FromJsonTo(&result); err != nil {
		return "", err
	}

	return result["ID"], nil
}

func (l *ConsulLock) acquire(key string) error {
	resp, err := goreq.Request{
		Method: "PUT",
		Uri:    l.consulAddr + "/v1/kv/" + fmt.Sprintf("%s?acquire=%s", l.lockKeyPrefix, l.lockSession),
	}.Do()
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		b, _ := resp.Body.ToString()
		return fmt.Errorf(b)
	}

	status, _ := resp.Body.ToString()
	fmt.Println("\n", status)
	if strings.TrimSpace(status) == "false" {
		return fmt.Errorf("lock already acquired by another process")
	}

	return nil
}

// Acquire acquires a lock on a key. A time-to-live time is set
// on the lock to ensure the lock is invalidated after the time is passed.
func (l *ConsulLock) Acquire(key string) error {

	var err error

	l.lockSession, err = l.createSession(int(LockTTL.Seconds()))
	if err != nil {
		return fmt.Errorf("failed to get lock: %s", err)
	}

	err = l.acquire(key)

	return err
}

// Release invalidates the lock previously acquired
func (l *ConsulLock) Release() error {
	return nil
}
<|endoftext|>


<commit_before>package extensions

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/telepresenceio/telepresence/v2/pkg/client"
)

// builtinExtensions is a function instead of a would-be-const var because its result includes the
// CLI version number, which might not be initialized yet at init-time (esp. during `go test`).
func builtinExtensions(_ context.Context) map[string]ExtensionInfo {
	return map[string]ExtensionInfo{
		// Real extensions won't have a "/" in the extname, by putting one builtin extension names
		// we can avoid clashes.
		"/builtin/telepresence": {
			Image: "${TELEPRESENCE_REGISTRY}/tel2:" + strings.TrimPrefix(client.Version(), "v"),
			Mechanisms: map[string]MechanismInfo{
				"tcp": {},
			},
		},
		// FIXME(lukeshu): We shouldn't compile in the info about the Ambassador Smart Agent
		// extension, but we don't yet have an installer to install the extension file; so this
		// metadata here is fine in the mean-time.
		"/builtin/ambassador": {
			Image:                   "grpc+https://${SYSTEMA_HOST}:${SYSTEMA_PORT}", // XXX: not using net.JoinHostPort means that setting SYSTEMA_HOST to an IPv6 address won't work
			RequiresAPIKeyOrLicense: true,
			Mechanisms: map[string]MechanismInfo{
				"http": {
					Preference: 100,
					Flags: map[string]FlagInfo{
						"match": {
							Type:    "string-array",
							Default: json.RawMessage(`["auto"]`),
							Usage: `` +
								`Rather than intercepting all traffic service, only intercept traffic that matches this "HTTP2_HEADER=REGEXP" specifier. ` +
								`Instead of a "--http-match=HTTP2_HEADER=REGEXP" pair, you may say "--http-match=auto", which will automatically select a unique matcher for your intercept. ` +
								`Alternatively, you may say "--http-match=all", which is a no-op, but will inhibit the default "--http-match=auto" when you are logged in. ` +
								`If this flag is given multiple times, then it will only intercept traffic that matches *all* of the specifiers. ` +
								`(default "auto" if you are logged in with 'telepresence login', default "all" otherwise)`,
						},
					},
				},
			},
		},
	}
}
<commit_msg>Use config to get registry for regular agent<commit_after>package extensions

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/telepresenceio/telepresence/v2/pkg/client"
)

// builtinExtensions is a function instead of a would-be-const var because its result includes the
// CLI version number, which might not be initialized yet at init-time (esp. during `go test`).
func builtinExtensions(ctx context.Context) map[string]ExtensionInfo {
	registry := client.GetConfig(ctx).Images.Registry
	version := strings.TrimPrefix(client.Version(), "v")
	image := fmt.Sprintf("%s/tel2:%s", registry, version)
	return map[string]ExtensionInfo{
		// Real extensions won't have a "/" in the extname, by putting one builtin extension names
		// we can avoid clashes.
		"/builtin/telepresence": {
			Image: image,
			Mechanisms: map[string]MechanismInfo{
				"tcp": {},
			},
		},
		// FIXME(lukeshu): We shouldn't compile in the info about the Ambassador Smart Agent
		// extension, but we don't yet have an installer to install the extension file; so this
		// metadata here is fine in the mean-time.
		"/builtin/ambassador": {
			Image:                   "grpc+https://${SYSTEMA_HOST}:${SYSTEMA_PORT}", // XXX: not using net.JoinHostPort means that setting SYSTEMA_HOST to an IPv6 address won't work
			RequiresAPIKeyOrLicense: true,
			Mechanisms: map[string]MechanismInfo{
				"http": {
					Preference: 100,
					Flags: map[string]FlagInfo{
						"match": {
							Type:    "string-array",
							Default: json.RawMessage(`["auto"]`),
							Usage: `` +
								`Rather than intercepting all traffic service, only intercept traffic that matches this "HTTP2_HEADER=REGEXP" specifier. ` +
								`Instead of a "--http-match=HTTP2_HEADER=REGEXP" pair, you may say "--http-match=auto", which will automatically select a unique matcher for your intercept. ` +
								`Alternatively, you may say "--http-match=all", which is a no-op, but will inhibit the default "--http-match=auto" when you are logged in. ` +
								`If this flag is given multiple times, then it will only intercept traffic that matches *all* of the specifiers. ` +
								`(default "auto" if you are logged in with 'telepresence login', default "all" otherwise)`,
						},
					},
				},
			},
		},
	}
}
<|endoftext|>


<commit_before>package network

import (
	"errors"
	"log"
	"net"

	"github.com/huin/goupnp/dcps/internetgateway1"
	"github.com/huin/goupnp/scpd"
)

func getLocalIPAddressForGateway() (string, error) {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		return "", err
	}
	defer func() { _ = conn.Close() }()
	var addr string
	addr, _, err = net.SplitHostPort(conn.LocalAddr().String())
	return addr, err
}

// SetupForwarding contacts a local gateway via UPnP and instructs it
// to forward externalPort to localPort on the internal local IP address discovered.
func SetupForwarding(localPort, externalPort uint16) (string, error) {
	clients, _, err := internetgateway1.NewWANIPConnection1Clients()
	var localIP string
	localIP, err = getLocalIPAddressForGateway()
	for _, c := range clients {
		var disco *scpd.SCPD
		disco, err = c.ServiceClient.Service.RequestSCDP()
		if err == nil {
			if disco.GetAction("AddPortMapping") != nil {
				if disco.GetAction("DeletePortMapping") != nil {
					if err = c.DeletePortMapping("", externalPort, "TCP"); err != nil {
						log.Printf("Deleting port forwarding mapping for %d: %s\n",
							externalPort, err)
					}
				}
				err = c.AddPortMapping("", externalPort, "TCP", localPort, localIP, true,
					"Rover", 0)
				if err == nil {
					if disco.GetAction("GetExternalIPAddress") != nil {
						ip, _ := c.GetExternalIPAddress()
						return ip, nil
					}
					return "", nil
				}
			}
		}
	}
	if err == nil {
		err = errors.New("No WAN IP gateways providing port mapping functions discovered")
	}
	return "", err
}
<commit_msg>Fix error propagation for WAN discovery<commit_after>package network

import (
	"errors"
	"log"
	"net"

	"github.com/huin/goupnp/dcps/internetgateway1"
	"github.com/huin/goupnp/scpd"
)

func getLocalIPAddressForGateway() (string, error) {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		return "", err
	}
	defer func() { _ = conn.Close() }()
	var addr string
	addr, _, err = net.SplitHostPort(conn.LocalAddr().String())
	return addr, err
}

// SetupForwarding contacts a local gateway via UPnP and instructs it
// to forward externalPort to localPort on the internal local IP address discovered.
func SetupForwarding(localPort, externalPort uint16) (string, error) {
	clients, _, err := internetgateway1.NewWANIPConnection1Clients()
	if err == nil {
		var localIP string
		localIP, err = getLocalIPAddressForGateway()
		for _, c := range clients {
			var disco *scpd.SCPD
			disco, err = c.ServiceClient.Service.RequestSCDP()
			if err == nil {
				if disco.GetAction("AddPortMapping") != nil {
					if disco.GetAction("DeletePortMapping") != nil {
						if err = c.DeletePortMapping("", externalPort, "TCP"); err != nil {
							log.Printf("Deleting port forwarding mapping for %d: %s\n",
								externalPort, err)
						}
					}
					err = c.AddPortMapping("", externalPort, "TCP", localPort, localIP, true,
						"Rover", 0)
					if err == nil {
						if disco.GetAction("GetExternalIPAddress") != nil {
							ip, _ := c.GetExternalIPAddress()
							return ip, nil
						}
						return "", nil
					}
				}
			}
		}
		if err == nil {
			err = errors.New("No WAN IP gateways providing port mapping functions discovered")
		}
	}
	return "", err
}
<|endoftext|>