<commit_before>package janus

import (
	log "github.com/Sirupsen/logrus"
	"github.com/etcinit/speedbump"
	"github.com/hellofresh/ginger-middleware/mongodb"
	"github.com/urfave/negroni"
	"gopkg.in/alexcesaro/statsd.v2"
	"gopkg.in/redis.v3"
)

var APILoader = APIDefinitionLoader{}

type APIManager struct {
	proxyRegister *ProxyRegister
	redisClient   *redis.Client
	accessor      *mongodb.DatabaseAccessor
}

// NewAPIManager creates a new instance of the api manager
func NewAPIManager(router Router, redisClient *redis.Client, accessor *mongodb.DatabaseAccessor, statsdClient *statsd.Client) *APIManager {
	proxyRegister := &ProxyRegister{Router: router, statsdClient: statsdClient}
	return &APIManager{proxyRegister, redisClient, accessor}
}

// Load loads all api specs from a datasource
func (m *APIManager) Load() {
	oauthManager := &OAuthManager{m.redisClient}

	oAuthServers := m.getOAuthServers()
	go m.LoadOAuthServers(oAuthServers, oauthManager)

	specs := m.getAPISpecs()
	go m.LoadApps(specs, oauthManager)
}

// LoadApps load application middleware
func (m *APIManager) LoadApps(apiSpecs []*APISpec, oauthManager *OAuthManager) {
	log.Debug("Loading API configurations")

	for _, referenceSpec := range apiSpecs {
		var skip bool

		//Validates the proxy
		skip = validateProxy(referenceSpec.Proxy)
		if false == referenceSpec.Active {
			log.Debug("API is not active, skiping...")
			skip = false
		}

		if skip {
			hasher := speedbump.PerSecondHasher{}
			limit := referenceSpec.RateLimit.Limit
			limiter := speedbump.NewLimiter(m.redisClient, hasher, limit)

			mw := &Middleware{referenceSpec}
			var beforeHandlers = []negroni.HandlerFunc{
				CreateMiddleware(&RateLimitMiddleware{mw, limiter, hasher, limit}),
				CreateMiddleware(&CorsMiddleware{mw}),
			}

			if referenceSpec.UseOauth2 {
				beforeHandlers = append(beforeHandlers, CreateMiddleware(&Oauth2KeyExists{mw, oauthManager}))
			}

			m.proxyRegister.Register(referenceSpec.Proxy, beforeHandlers, nil)
			log.Debug("Proxy registered")
		} else {
			log.Error("Listen path is empty, skipping...")
		}
	}
}

// LoadOAuthServers loads and register the oauth servers
func (m *APIManager) LoadOAuthServers(oauthServers []*OAuthSpec, oauthManager *OAuthManager) {
	log.Debug("Loading OAuth servers configurations")

	var beforeHandlers []negroni.HandlerFunc
	var handlers []negroni.HandlerFunc
	oauthRegister := &OAuthRegister{}

	for _, oauthServer := range oauthServers {
		beforeHandlers = append(beforeHandlers, CreateMiddleware(&Oauth2Secret{oauthServer}))
		handlers = append(handlers, CreateMiddleware(&OAuthMiddleware{oauthManager, oauthServer}))
		oauthServer.OAuthManager = &OAuthManager{m.redisClient}
		proxies := oauthRegister.GetProxiesForServer(oauthServer.OAuth)
		m.proxyRegister.RegisterMany(proxies, beforeHandlers, handlers)
	}

	log.Debug("Done loading OAuth servers configurations")
}

//getAPISpecs Load application specs from datasource
func (m *APIManager) getAPISpecs() []*APISpec {
	log.Debug("Using App Configuration from Mongo DB")
	return APILoader.LoadDefinitionsFromDatastore(m.accessor.Session)
}

//getOAuthServers Load oauth servers from datasource
func (m *APIManager) getOAuthServers() []*OAuthSpec {
	log.Debug("Using Oauth servers configuration from Mongo DB")
	return APILoader.LoadOauthServersFromDatastore(m.accessor.Session)
}
<commit_msg>Rename middlewares in api manager<commit_after>package janus

import (
	log "github.com/Sirupsen/logrus"
	"github.com/etcinit/speedbump"
	"github.com/hellofresh/ginger-middleware/mongodb"
	"github.com/urfave/negroni"
	"gopkg.in/alexcesaro/statsd.v2"
	"gopkg.in/redis.v3"
)

var APILoader = APIDefinitionLoader{}

type APIManager struct {
	proxyRegister *ProxyRegister
	redisClient   *redis.Client
	accessor      *mongodb.DatabaseAccessor
}

// NewAPIManager creates a new instance of the api manager
func NewAPIManager(router Router, redisClient *redis.Client, accessor *mongodb.DatabaseAccessor, statsdClient *statsd.Client) *APIManager {
	proxyRegister := &ProxyRegister{Router: router, statsdClient: statsdClient}
	return &APIManager{proxyRegister, redisClient, accessor}
}

// Load loads all api specs from a datasource
func (m *APIManager) Load() {
	oauthManager := &OAuthManager{m.redisClient}

	oAuthServers := m.getOAuthServers()
	go m.LoadOAuthServers(oAuthServers, oauthManager)

	specs := m.getAPISpecs()
	go m.LoadApps(specs, oauthManager)
}

// LoadApps load application middleware
func (m *APIManager) LoadApps(apiSpecs []*APISpec, oauthManager *OAuthManager) {
	log.Debug("Loading API configurations")

	for _, referenceSpec := range apiSpecs {
		var skip bool

		//Validates the proxy
		skip = validateProxy(referenceSpec.Proxy)
		if false == referenceSpec.Active {
			log.Debug("API is not active, skiping...")
			skip = false
		}

		if skip {
			hasher := speedbump.PerSecondHasher{}
			limit := referenceSpec.RateLimit.Limit
			limiter := speedbump.NewLimiter(m.redisClient, hasher, limit)

			mw := &Middleware{referenceSpec}
			var beforeHandlers = []negroni.HandlerFunc{
				CreateMiddleware(&RateLimitMiddleware{mw, limiter, hasher, limit}),
				CreateMiddleware(&CorsMiddleware{mw}),
			}

			if referenceSpec.UseOauth2 {
				beforeHandlers = append(beforeHandlers, CreateMiddleware(&Oauth2KeyExistsMiddleware{mw, oauthManager}))
			}

			m.proxyRegister.Register(referenceSpec.Proxy, beforeHandlers, nil)
			log.Debug("Proxy registered")
		} else {
			log.Error("Listen path is empty, skipping...")
		}
	}
}

// LoadOAuthServers loads and register the oauth servers
func (m *APIManager) LoadOAuthServers(oauthServers []*OAuthSpec, oauthManager *OAuthManager) {
	log.Debug("Loading OAuth servers configurations")

	var beforeHandlers []negroni.HandlerFunc
	var handlers []negroni.HandlerFunc
	oauthRegister := &OAuthRegister{}

	for _, oauthServer := range oauthServers {
		beforeHandlers = append(beforeHandlers, CreateMiddleware(&Oauth2SecretMiddleware{oauthServer}))
		handlers = append(handlers, CreateMiddleware(&OAuthMiddleware{oauthManager, oauthServer}))
		oauthServer.OAuthManager = &OAuthManager{m.redisClient}
		proxies := oauthRegister.GetProxiesForServer(oauthServer.OAuth)
		m.proxyRegister.RegisterMany(proxies, beforeHandlers, handlers)
	}

	log.Debug("Done loading OAuth servers configurations")
}

//getAPISpecs Load application specs from datasource
func (m *APIManager) getAPISpecs() []*APISpec {
	log.Debug("Using App Configuration from Mongo DB")
	return APILoader.LoadDefinitionsFromDatastore(m.accessor.Session)
}

//getOAuthServers Load oauth servers from datasource
func (m *APIManager) getOAuthServers() []*OAuthSpec {
	log.Debug("Using Oauth servers configuration from Mongo DB")
	return APILoader.LoadOauthServersFromDatastore(m.accessor.Session)
}
<|endoftext|>


<commit_before>// ISO/IEC 2000 APL Required Character Set
// see also /usr/share/X11/xkb/symbols/apl
package main

import "fmt"

var charmap = map[rune]string{
	'⍺': "alpha",
	'↓': "down arrow",
	'←': "left arrow",
	'→': "right arrow",
	'↑': "up arrow",
	'-': "bar",
	' ': "blank",
	'{': "left brace",
	'}': "right brace",
	'[': "left bracket",
	']': "right bracket",
	'∨': "down caret",
	'⍱': "down caret tilde",
	'<': "left caret",
	'>': "right caret",
	'∧': "up caret",
	'⍲': "up caret tilde",
	'○': "circle",
	'⍉': "circle backslash",
	'⊖': "circle bar",
	'⍟': "circle star",
	'⌽': "circle stile",
	':': "colon",
	',': "comma",
	'⍪': "comma bar",
	'∇': "del",
	'⍒': "del stile",
	'⍫': "del tilde",
	'∆': "delta",
	'⍋': "delta stile",
	'⍙': "delta underbar",
	'¨': "diaeresis",
	'⋄': "diamond",
	'÷': "divide",
	'$': "dollar sign",
	'.': "dot",
	'∊': "epsion",
	'=': "equal",
	'≥': "greater-than or equal",
	'⍳': "iota",
	'∘': "jot",
	'≤': "less-than or equal",
	'×': "miltiply",
	'≠': "not equal",
	'¯': "overbar",
	'(': "left parenthesis",
	')': "right parenthesis",
	'+': "plus",
	'⎕': "quad",
	'⌹': "quad divide",
	'?': "query",
	'⍤': "diaeresis jot",
	'\'': "quote",
	'!': "quote dot",
	'⍞': "quate quad",
	'⍴': "rho",
	';': "semicolon",
	'∪': "down shoe",
	'⊂': "left shoe",
	'⊃': "right shoe",
	'∩': "up shoe",
	'⍝': "up shoe jot",
	'/': "slash",
	'\\': "back slash",
	'⌿': "slash bar",
	'⍀': "back slash bar",
	'*': "star",
	'|': "stile",
	'⌊': "down stile",
	'⌈': "up stile",
	'⊥': "up tack",
	'⍎': "up tack jot",
	'⊢': "right tack",
	'⊣': "left tack",
	'⊤': "down tack",
	'⍕': "down tack jot",
	'~': "tilde",
	'_': "underbar",
	'⍨': "diaeresis tilde",
	'⍵': "omega",
	'≡': "equal underbar",
}

func scan(chars string) {
	for _, c := range chars {
		if name, ok := charmap[c]; ok {
			fmt.Println(string(c), name)
		} else {
			fmt.Println(string(c), "... missing")
		}
	}
}

func main() {
	scan("⋄⌶⍫⍒⍋⌽⍉⊖⍟⍱⍲!⌹?⍵⍷⍴⍨↑↓⍸⍥⍣⍞⍬⍺⌈⌊_∇∆⍤'⌷≡≢⊣⍕⊂⊃∩∪⊥⊤|⍪⍙⍠")
	scan("⋄¨¯<≤=≥>≠∨∧×÷?⍵∊⍴~↑↓⍳○*←→⍺⌈⌊_∇∆∘'⎕⍎⍕⊢⍎⊂⊃∩∪⊥⊤|⍝⍀⌿")
}
<commit_msg>gofmt<commit_after>// ISO/IEC 2000 APL Required Character Set
// see also /usr/share/X11/xkb/symbols/apl
package main

import "fmt"

var charmap = map[rune]string{
	'⍺':  "alpha",
	'↓':  "down arrow",
	'←':  "left arrow",
	'→':  "right arrow",
	'↑':  "up arrow",
	'-':  "bar",
	' ':  "blank",
	'{':  "left brace",
	'}':  "right brace",
	'[':  "left bracket",
	']':  "right bracket",
	'∨':  "down caret",
	'⍱':  "down caret tilde",
	'<':  "left caret",
	'>':  "right caret",
	'∧':  "up caret",
	'⍲':  "up caret tilde",
	'○':  "circle",
	'⍉':  "circle backslash",
	'⊖':  "circle bar",
	'⍟':  "circle star",
	'⌽':  "circle stile",
	':':  "colon",
	',':  "comma",
	'⍪':  "comma bar",
	'∇':  "del",
	'⍒':  "del stile",
	'⍫':  "del tilde",
	'∆':  "delta",
	'⍋':  "delta stile",
	'⍙':  "delta underbar",
	'¨':  "diaeresis",
	'⋄':  "diamond",
	'÷':  "divide",
	'$':  "dollar sign",
	'.':  "dot",
	'∊':  "epsion",
	'=':  "equal",
	'≥':  "greater-than or equal",
	'⍳':  "iota",
	'∘':  "jot",
	'≤':  "less-than or equal",
	'×':  "miltiply",
	'≠':  "not equal",
	'¯':  "overbar",
	'(':  "left parenthesis",
	')':  "right parenthesis",
	'+':  "plus",
	'⎕':  "quad",
	'⌹':  "quad divide",
	'?':  "query",
	'⍤':  "diaeresis jot",
	'\'': "quote",
	'!':  "quote dot",
	'⍞':  "quate quad",
	'⍴':  "rho",
	';':  "semicolon",
	'∪':  "down shoe",
	'⊂':  "left shoe",
	'⊃':  "right shoe",
	'∩':  "up shoe",
	'⍝':  "up shoe jot",
	'/':  "slash",
	'\\': "back slash",
	'⌿':  "slash bar",
	'⍀':  "back slash bar",
	'*':  "star",
	'|':  "stile",
	'⌊':  "down stile",
	'⌈':  "up stile",
	'⊥':  "up tack",
	'⍎':  "up tack jot",
	'⊢':  "right tack",
	'⊣':  "left tack",
	'⊤':  "down tack",
	'⍕':  "down tack jot",
	'~':  "tilde",
	'_':  "underbar",
	'⍨':  "diaeresis tilde",
	'⍵':  "omega",
	'≡':  "equal underbar",
}

func scan(chars string) {
	for _, c := range chars {
		if name, ok := charmap[c]; ok {
			fmt.Println(string(c), name)
		} else {
			fmt.Println(string(c), "... missing")
		}
	}
}

func main() {
	scan("⋄⌶⍫⍒⍋⌽⍉⊖⍟⍱⍲!⌹?⍵⍷⍴⍨↑↓⍸⍥⍣⍞⍬⍺⌈⌊_∇∆⍤'⌷≡≢⊣⍕⊂⊃∩∪⊥⊤|⍪⍙⍠")
	scan("⋄¨¯<≤=≥>≠∨∧×÷?⍵∊⍴~↑↓⍳○*←→⍺⌈⌊_∇∆∘'⎕⍎⍕⊢⍎⊂⊃∩∪⊥⊤|⍝⍀⌿")
}
<|endoftext|>


<commit_before>// +build !race

package command

import (
	"context"
	"fmt"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/hashicorp/vault/vault/diagnose"
	"github.com/mitchellh/cli"
)

func testOperatorDiagnoseCommand(tb testing.TB) *OperatorDiagnoseCommand {
	tb.Helper()

	ui := cli.NewMockUi()
	return &OperatorDiagnoseCommand{
		diagnose: diagnose.New(ioutil.Discard),
		BaseCommand: &BaseCommand{
			UI: ui,
		},
		skipEndEnd: true,
	}
}

func TestOperatorDiagnoseCommand_Run(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name     string
		args     []string
		expected []*diagnose.Result
	}{
		{
			"diagnose_ok",
			[]string{
				"-config", "./server/test-fixtures/config_diagnose_ok.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "operating system",
					Status: diagnose.OkStatus,
					Children: []*diagnose.Result{
						{
							Name:   "open file limits",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "disk usage",
							Status: diagnose.OkStatus,
						},
					},
				},

				{
					Name:   "parse-config",
					Status: diagnose.OkStatus,
				},
				{
					Name:   "init-listeners",
					Status: diagnose.WarningStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-listeners",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "check-listener-tls",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								"TLS is disabled in a Listener config stanza.",
							},
						},
					},
				},
				{
					Name:   "storage",
					Status: diagnose.OkStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-storage-tls-consul",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.OkStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_invalid_storage",
			[]string{
				"-config", "./server/test-fixtures/nostore_config.hcl",
			},
			[]*diagnose.Result{
				{
					Name:    "storage",
					Status:  diagnose.ErrorStatus,
					Message: "no storage stanza found in config",
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.ErrorStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_listener_config_ok",
			[]string{
				"-config", "./server/test-fixtures/tls_config_ok.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "init-listeners",
					Status: diagnose.OkStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-listeners",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "check-listener-tls",
							Status: diagnose.OkStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_invalid_https_storage",
			[]string{
				"-config", "./server/test-fixtures/config_bad_https_storage.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "storage",
					Status: diagnose.ErrorStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:    "test-storage-tls-consul",
							Status:  diagnose.ErrorStatus,
							Message: "expired",
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.OkStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_invalid_https_hastorage",
			[]string{
				"-config", "./server/test-fixtures/config_diagnose_hastorage_bad_https.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "storage",
					Status: diagnose.WarningStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-storage-tls-consul",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								"consul storage does not connect to local agent, but directly to server",
							},
						},
					},
				},
				{
					Name:   "setup-ha-storage",
					Status: diagnose.ErrorStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-ha-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								"consul storage does not connect to local agent, but directly to server",
							},
						},
						{
							Name:    "test-ha-storage-tls-consul",
							Status:  diagnose.ErrorStatus,
							Message: "x509: certificate has expired or is not yet valid",
						},
					},
				},
				{
					Name:   "find-cluster-addr",
					Status: diagnose.ErrorStatus,
				},
			},
		},
		{
			"diagnose_invalid_https_sr",
			[]string{
				"-config", "./server/test-fixtures/diagnose_bad_https_consul_sr.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "service-discovery",
					Status: diagnose.ErrorStatus,
					Children: []*diagnose.Result{
						{
							Name:    "test-serviceregistration-tls-consul",
							Status:  diagnose.ErrorStatus,
							Message: "failed to verify certificate: x509: certificate has expired or is not yet valid",
						},
						{
							Name:   "test-consul-direct-access-service-discovery",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								diagnose.DirAccessErr,
							},
						},
					},
				},
			},
		},
		{
			"diagnose_direct_storage_access",
			[]string{
				"-config", "./server/test-fixtures/diagnose_ok_storage_direct_access.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "storage",
					Status: diagnose.WarningStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-storage-tls-consul",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								diagnose.DirAccessErr,
							},
						},
					},
				},
			},
		},
	}

	t.Run("validations", func(t *testing.T) {
		t.Parallel()

		for _, tc := range cases {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				t.Parallel()
				client, closer := testVaultServer(t)
				defer closer()

				cmd := testOperatorDiagnoseCommand(t)
				cmd.client = client

				cmd.Run(tc.args)
				result := cmd.diagnose.Finalize(context.Background())

				if err := compareResults(tc.expected, result.Children); err != nil {
					t.Fatalf("Did not find expected test results: %v", err)
					t.Fatal(result.String())
				}
			})
		}
	})
}

func compareResults(expected []*diagnose.Result, actual []*diagnose.Result) error {
	for _, exp := range expected {
		found := false
		// Check them all so we don't have to be order specific
		for _, act := range actual {
			if exp.Name == act.Name {
				found = true
				if err := compareResult(exp, act); err != nil {
					return err
				}
				break
			}
		}
		if !found {
			return fmt.Errorf("could not find expected test result: %s", exp.Name)
		}
	}
	return nil
}

func compareResult(exp *diagnose.Result, act *diagnose.Result) error {
	if exp.Name != act.Name {
		return fmt.Errorf("names mismatch: %s vs %s", exp.Name, act.Name)
	}
	if exp.Status != act.Status {
		if act.Status != diagnose.OkStatus {
			return fmt.Errorf("section %s, status mismatch: %s vs %s, got error %s", exp.Name, exp.Status, act.Status, act.Message)

		}
		return fmt.Errorf("section %s, status mismatch: %s vs %s", exp.Name, exp.Status, act.Status)
	}
	if exp.Message != "" && exp.Message != act.Message && !strings.Contains(act.Message, exp.Message) {
		return fmt.Errorf("section %s, message not found: %s in %s", exp.Name, exp.Message, act.Message)
	}
	if len(exp.Warnings) != len(act.Warnings) {
		return fmt.Errorf("section %s, warning count mismatch: %d vs %d", exp.Name, len(exp.Warnings), len(act.Warnings))
	}
	for j := range exp.Warnings {
		if !strings.Contains(act.Warnings[j], exp.Warnings[j]) {
			return fmt.Errorf("section %s, warning message not found: %s in %s", exp.Name, exp.Warnings[j], act.Warnings[j])
		}
	}
	if len(exp.Children) > len(act.Children) {
		errStrings := []string{}
		for _, c := range act.Children {
			errStrings = append(errStrings, fmt.Sprintf("%+v", c))
		}
		return fmt.Errorf(strings.Join(errStrings, ","))
	}

	if len(exp.Children) > 0 {
		return compareResults(exp.Children, act.Children)
	}

	if len(exp.Children) > 0 {
		return compareResults(exp.Children, act.Children)
	}
	return nil
}
<commit_msg>Remove duplicate children test (#11751)<commit_after>// +build !race

package command

import (
	"context"
	"fmt"
	"io/ioutil"
	"strings"
	"testing"

	"github.com/hashicorp/vault/vault/diagnose"
	"github.com/mitchellh/cli"
)

func testOperatorDiagnoseCommand(tb testing.TB) *OperatorDiagnoseCommand {
	tb.Helper()

	ui := cli.NewMockUi()
	return &OperatorDiagnoseCommand{
		diagnose: diagnose.New(ioutil.Discard),
		BaseCommand: &BaseCommand{
			UI: ui,
		},
		skipEndEnd: true,
	}
}

func TestOperatorDiagnoseCommand_Run(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name     string
		args     []string
		expected []*diagnose.Result
	}{
		{
			"diagnose_ok",
			[]string{
				"-config", "./server/test-fixtures/config_diagnose_ok.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "operating system",
					Status: diagnose.OkStatus,
					Children: []*diagnose.Result{
						{
							Name:   "open file limits",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "disk usage",
							Status: diagnose.OkStatus,
						},
					},
				},

				{
					Name:   "parse-config",
					Status: diagnose.OkStatus,
				},
				{
					Name:   "init-listeners",
					Status: diagnose.WarningStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-listeners",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "check-listener-tls",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								"TLS is disabled in a Listener config stanza.",
							},
						},
					},
				},
				{
					Name:   "storage",
					Status: diagnose.OkStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-storage-tls-consul",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.OkStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_invalid_storage",
			[]string{
				"-config", "./server/test-fixtures/nostore_config.hcl",
			},
			[]*diagnose.Result{
				{
					Name:    "storage",
					Status:  diagnose.ErrorStatus,
					Message: "no storage stanza found in config",
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.ErrorStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_listener_config_ok",
			[]string{
				"-config", "./server/test-fixtures/tls_config_ok.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "init-listeners",
					Status: diagnose.OkStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-listeners",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "check-listener-tls",
							Status: diagnose.OkStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_invalid_https_storage",
			[]string{
				"-config", "./server/test-fixtures/config_bad_https_storage.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "storage",
					Status: diagnose.ErrorStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:    "test-storage-tls-consul",
							Status:  diagnose.ErrorStatus,
							Message: "expired",
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.OkStatus,
						},
					},
				},
			},
		},
		{
			"diagnose_invalid_https_hastorage",
			[]string{
				"-config", "./server/test-fixtures/config_diagnose_hastorage_bad_https.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "storage",
					Status: diagnose.WarningStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-storage-tls-consul",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								"consul storage does not connect to local agent, but directly to server",
							},
						},
					},
				},
				{
					Name:   "setup-ha-storage",
					Status: diagnose.ErrorStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-ha-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								"consul storage does not connect to local agent, but directly to server",
							},
						},
						{
							Name:    "test-ha-storage-tls-consul",
							Status:  diagnose.ErrorStatus,
							Message: "x509: certificate has expired or is not yet valid",
						},
					},
				},
				{
					Name:   "find-cluster-addr",
					Status: diagnose.ErrorStatus,
				},
			},
		},
		{
			"diagnose_invalid_https_sr",
			[]string{
				"-config", "./server/test-fixtures/diagnose_bad_https_consul_sr.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "service-discovery",
					Status: diagnose.ErrorStatus,
					Children: []*diagnose.Result{
						{
							Name:    "test-serviceregistration-tls-consul",
							Status:  diagnose.ErrorStatus,
							Message: "failed to verify certificate: x509: certificate has expired or is not yet valid",
						},
						{
							Name:   "test-consul-direct-access-service-discovery",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								diagnose.DirAccessErr,
							},
						},
					},
				},
			},
		},
		{
			"diagnose_direct_storage_access",
			[]string{
				"-config", "./server/test-fixtures/diagnose_ok_storage_direct_access.hcl",
			},
			[]*diagnose.Result{
				{
					Name:   "storage",
					Status: diagnose.WarningStatus,
					Children: []*diagnose.Result{
						{
							Name:   "create-storage-backend",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-storage-tls-consul",
							Status: diagnose.OkStatus,
						},
						{
							Name:   "test-consul-direct-access-storage",
							Status: diagnose.WarningStatus,
							Warnings: []string{
								diagnose.DirAccessErr,
							},
						},
					},
				},
			},
		},
	}

	t.Run("validations", func(t *testing.T) {
		t.Parallel()

		for _, tc := range cases {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				t.Parallel()
				client, closer := testVaultServer(t)
				defer closer()

				cmd := testOperatorDiagnoseCommand(t)
				cmd.client = client

				cmd.Run(tc.args)
				result := cmd.diagnose.Finalize(context.Background())

				if err := compareResults(tc.expected, result.Children); err != nil {
					t.Fatalf("Did not find expected test results: %v", err)
					t.Fatal(result.String())
				}
			})
		}
	})
}

func compareResults(expected []*diagnose.Result, actual []*diagnose.Result) error {
	for _, exp := range expected {
		found := false
		// Check them all so we don't have to be order specific
		for _, act := range actual {
			if exp.Name == act.Name {
				found = true
				if err := compareResult(exp, act); err != nil {
					return err
				}
				break
			}
		}
		if !found {
			return fmt.Errorf("could not find expected test result: %s", exp.Name)
		}
	}
	return nil
}

func compareResult(exp *diagnose.Result, act *diagnose.Result) error {
	if exp.Name != act.Name {
		return fmt.Errorf("names mismatch: %s vs %s", exp.Name, act.Name)
	}
	if exp.Status != act.Status {
		if act.Status != diagnose.OkStatus {
			return fmt.Errorf("section %s, status mismatch: %s vs %s, got error %s", exp.Name, exp.Status, act.Status, act.Message)

		}
		return fmt.Errorf("section %s, status mismatch: %s vs %s", exp.Name, exp.Status, act.Status)
	}
	if exp.Message != "" && exp.Message != act.Message && !strings.Contains(act.Message, exp.Message) {
		return fmt.Errorf("section %s, message not found: %s in %s", exp.Name, exp.Message, act.Message)
	}
	if len(exp.Warnings) != len(act.Warnings) {
		return fmt.Errorf("section %s, warning count mismatch: %d vs %d", exp.Name, len(exp.Warnings), len(act.Warnings))
	}
	for j := range exp.Warnings {
		if !strings.Contains(act.Warnings[j], exp.Warnings[j]) {
			return fmt.Errorf("section %s, warning message not found: %s in %s", exp.Name, exp.Warnings[j], act.Warnings[j])
		}
	}
	if len(exp.Children) > len(act.Children) {
		errStrings := []string{}
		for _, c := range act.Children {
			errStrings = append(errStrings, fmt.Sprintf("%+v", c))
		}
		return fmt.Errorf(strings.Join(errStrings, ","))
	}

	if len(exp.Children) > 0 {
		return compareResults(exp.Children, act.Children)
	}

	return nil
}
<|endoftext|>


<commit_before>package awskms

import (
	"bytes"
	"crypto/sha1"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/dcoker/biscuit/keymanager"
	"github.com/dcoker/biscuit/shared"
	"github.com/dcoker/biscuit/store"
	"gopkg.in/alecthomas/kingpin.v2"
)

type kmsGrantsCreate struct {
	name,
	granteePrincipal,
	retiringPrincipal,
	filename   *string
	operations *[]string
	allNames   *bool
}

// NewKmsGrantsCreate constructs the command to create a grant.
func NewKmsGrantsCreate(c *kingpin.CmdClause) shared.Command {
	params := &kmsGrantsCreate{}
	params.name = c.Arg("name", "Name of the secret to grant access to.").Required().String()
	params.allNames = c.Flag("all-names", "If set, the grant allows the grantee to decrypt any values encrypted under "+
		"the keys that the named secret is encrypted with.").Default("false").Bool()
	params.granteePrincipal = c.Flag("grantee-principal", "The ARN that will be granted "+
		"additional privileges.").Short('g').PlaceHolder("ARN").Required().String()
	params.retiringPrincipal = c.Flag("retiring-principal", "The ARN that can retire the "+
		"grant.").Short('e').PlaceHolder("ARN").String()
	params.operations = operationsFlag(c)
	params.filename = shared.FilenameFlag(c)
	return params
}

type grantsCreatedOutput struct {
	Name string
	// Alias -> Region -> Grant
	Aliases map[string]map[string]grantDetails
}

type grantDetails struct {
	GrantID,
	GrantToken string
}

// Run runs the command.
func (w *kmsGrantsCreate) Run() error {
	database := store.NewFileStore(*w.filename)
	values, err := database.Get(*w.name)
	if err != nil {
		return err
	}
	values = values.FilterByKeyManager(keymanager.KmsLabel)

	aliases, err := resolveValuesToAliasesAndRegions(values)
	if err != nil {
		return err
	}

	granteeArn, retireeArn, err := resolveGranteeArns(*w.granteePrincipal, *w.retiringPrincipal)

	// The template from which grants in each region are created.
	createGrantInput := kms.CreateGrantInput{
		Operations:       aws.StringSlice(*w.operations),
		GranteePrincipal: &granteeArn,
	}
	if !*w.allNames {
		createGrantInput.Constraints = &kms.GrantConstraints{
			EncryptionContextSubset: map[string]*string{"SecretName": w.name},
		}
	}
	if len(retireeArn) > 0 {
		createGrantInput.RetiringPrincipal = &retireeArn
	}

	grantName, err := computeGrantName(createGrantInput)
	if err != nil {
		return err
	}
	createGrantInput.Name = aws.String(grantName)

	output := grantsCreatedOutput{
		Name:    grantName,
		Aliases: make(map[string]map[string]grantDetails),
	}
	for alias, regionList := range aliases {
		mrk, err := NewMultiRegionKey(alias, regionList, "")
		if err != nil {
			return err
		}
		results, err := mrk.AddGrant(createGrantInput)
		if err != nil {
			return err
		}
		regionToGrantDetails := make(map[string]grantDetails)
		for region, grant := range results {
			regionToGrantDetails[region] = grantDetails{
				GrantID:    *grant.GrantId,
				GrantToken: *grant.GrantToken}
		}
		output.Aliases[alias] = regionToGrantDetails
	}
	fmt.Print(shared.MustYaml(output))
	return nil
}

func computeGrantName(input kms.CreateGrantInput) (string, error) {
	callerIdentity, err := sts.New(session.New()).GetCallerIdentity(nil)
	if err != nil {
		return "", err
	}

	var buf bytes.Buffer
	gob.Register(kms.CreateGrantInput{})
	encoder := gob.NewEncoder(&buf)
	if err := encoder.Encode([]interface{}{input, callerIdentity.Arn}); err != nil {
		panic(err)
	}
	hashed := sha1.Sum(buf.Bytes())
	return GrantPrefix + hex.EncodeToString(hashed[:])[:10], nil
}

func resolveValuesToAliasesAndRegions(values store.ValueList) (map[string][]string, error) {
	// The KeyID field may refer to a key/ or alias/ ARN. We need to resolve the alias for any key/ ARN
	// so that we can act on them across multiple regions. This loop resolves key/ ARNs into their appropriate
	// aliases, and maintains a list of regions for each alias.
	aliases := make(map[string][]string)
	for _, v := range values {
		arn, err := keymanager.NewARN(v.KeyID)
		if err != nil {
			return nil, err
		}
		if arn.IsKmsAlias() {
			aliases["alias/"+arn.Resource] = append(aliases["alias/"+arn.Resource], arn.Region)
		} else if arn.IsKmsKey() {
			region := arn.Region
			client := kmsHelper{kms.New(session.New(&aws.Config{Region: &region}))}
			alias, err := client.GetAliasByKeyID(arn.Resource)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: Unable to find an alias for this key: %s\n", v.KeyID, err)
				return nil, err
			}
			aliases[alias] = append(aliases[alias], arn.Region)
		} else {
			return nil, err
		}
	}
	return aliases, nil
}

func resolveGranteeArns(granteePrincipal, retiringPrincipal string) (string, string, error) {
	stsClient := sts.New(session.New(&aws.Config{}))
	callerIdentity, err := stsClient.GetCallerIdentity(nil)
	if err != nil {
		return "", "", err
	}
	granteeArn := cleanArn(*callerIdentity.Account, granteePrincipal)
	if len(granteeArn) == 0 {
		return "", "", errors.New("grantee ARN must not be empty string")
	}
	retireeArn := cleanArn(*callerIdentity.Account, retiringPrincipal)
	return granteeArn, retireeArn, nil
}
<commit_msg>Minor formatting fix to kmsgrantcreate.<commit_after>package awskms

import (
	"bytes"
	"crypto/sha1"
	"encoding/gob"
	"encoding/hex"
	"errors"
	"fmt"
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/kms"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/dcoker/biscuit/keymanager"
	"github.com/dcoker/biscuit/shared"
	"github.com/dcoker/biscuit/store"
	"gopkg.in/alecthomas/kingpin.v2"
)

type kmsGrantsCreate struct {
	name,
	granteePrincipal,
	retiringPrincipal,
	filename *string
	operations *[]string
	allNames   *bool
}

// NewKmsGrantsCreate constructs the command to create a grant.
func NewKmsGrantsCreate(c *kingpin.CmdClause) shared.Command {
	params := &kmsGrantsCreate{}
	params.name = c.Arg("name", "Name of the secret to grant access to.").Required().String()
	params.allNames = c.Flag("all-names", "If set, the grant allows the grantee to decrypt any values encrypted under "+
		"the keys that the named secret is encrypted with.").Default("false").Bool()
	params.granteePrincipal = c.Flag("grantee-principal", "The ARN that will be granted "+
		"additional privileges.").Short('g').PlaceHolder("ARN").Required().String()
	params.retiringPrincipal = c.Flag("retiring-principal", "The ARN that can retire the "+
		"grant.").Short('e').PlaceHolder("ARN").String()
	params.operations = operationsFlag(c)
	params.filename = shared.FilenameFlag(c)
	return params
}

type grantsCreatedOutput struct {
	Name string
	// Alias -> Region -> Grant
	Aliases map[string]map[string]grantDetails
}

type grantDetails struct {
	GrantID,
	GrantToken string
}

// Run runs the command.
func (w *kmsGrantsCreate) Run() error {
	database := store.NewFileStore(*w.filename)
	values, err := database.Get(*w.name)
	if err != nil {
		return err
	}
	values = values.FilterByKeyManager(keymanager.KmsLabel)

	aliases, err := resolveValuesToAliasesAndRegions(values)
	if err != nil {
		return err
	}

	granteeArn, retireeArn, err := resolveGranteeArns(*w.granteePrincipal, *w.retiringPrincipal)

	// The template from which grants in each region are created.
	createGrantInput := kms.CreateGrantInput{
		Operations:       aws.StringSlice(*w.operations),
		GranteePrincipal: &granteeArn,
	}
	if !*w.allNames {
		createGrantInput.Constraints = &kms.GrantConstraints{
			EncryptionContextSubset: map[string]*string{"SecretName": w.name},
		}
	}
	if len(retireeArn) > 0 {
		createGrantInput.RetiringPrincipal = &retireeArn
	}

	grantName, err := computeGrantName(createGrantInput)
	if err != nil {
		return err
	}
	createGrantInput.Name = aws.String(grantName)

	output := grantsCreatedOutput{
		Name:    grantName,
		Aliases: make(map[string]map[string]grantDetails),
	}
	for alias, regionList := range aliases {
		mrk, err := NewMultiRegionKey(alias, regionList, "")
		if err != nil {
			return err
		}
		results, err := mrk.AddGrant(createGrantInput)
		if err != nil {
			return err
		}
		regionToGrantDetails := make(map[string]grantDetails)
		for region, grant := range results {
			regionToGrantDetails[region] = grantDetails{
				GrantID:    *grant.GrantId,
				GrantToken: *grant.GrantToken}
		}
		output.Aliases[alias] = regionToGrantDetails
	}
	fmt.Print(shared.MustYaml(output))
	return nil
}

func computeGrantName(input kms.CreateGrantInput) (string, error) {
	callerIdentity, err := sts.New(session.New()).GetCallerIdentity(nil)
	if err != nil {
		return "", err
	}

	var buf bytes.Buffer
	gob.Register(kms.CreateGrantInput{})
	encoder := gob.NewEncoder(&buf)
	if err := encoder.Encode([]interface{}{input, callerIdentity.Arn}); err != nil {
		panic(err)
	}
	hashed := sha1.Sum(buf.Bytes())
	return GrantPrefix + hex.EncodeToString(hashed[:])[:10], nil
}

func resolveValuesToAliasesAndRegions(values store.ValueList) (map[string][]string, error) {
	// The KeyID field may refer to a key/ or alias/ ARN. We need to resolve the alias for any key/ ARN
	// so that we can act on them across multiple regions. This loop resolves key/ ARNs into their appropriate
	// aliases, and maintains a list of regions for each alias.
	aliases := make(map[string][]string)
	for _, v := range values {
		arn, err := keymanager.NewARN(v.KeyID)
		if err != nil {
			return nil, err
		}
		if arn.IsKmsAlias() {
			aliases["alias/"+arn.Resource] = append(aliases["alias/"+arn.Resource], arn.Region)
		} else if arn.IsKmsKey() {
			region := arn.Region
			client := kmsHelper{kms.New(session.New(&aws.Config{Region: &region}))}
			alias, err := client.GetAliasByKeyID(arn.Resource)
			if err != nil {
				fmt.Fprintf(os.Stderr, "%s: Unable to find an alias for this key: %s\n", v.KeyID, err)
				return nil, err
			}
			aliases[alias] = append(aliases[alias], arn.Region)
		} else {
			return nil, err
		}
	}
	return aliases, nil
}

func resolveGranteeArns(granteePrincipal, retiringPrincipal string) (string, string, error) {
	stsClient := sts.New(session.New(&aws.Config{}))
	callerIdentity, err := stsClient.GetCallerIdentity(nil)
	if err != nil {
		return "", "", err
	}
	granteeArn := cleanArn(*callerIdentity.Account, granteePrincipal)
	if len(granteeArn) == 0 {
		return "", "", errors.New("grantee ARN must not be empty string")
	}
	retireeArn := cleanArn(*callerIdentity.Account, retiringPrincipal)
	return granteeArn, retireeArn, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	log "github.com/Sirupsen/logrus"
)

func init() {
	http.HandleFunc("/compile", compile)
}

func compile(w http.ResponseWriter, r *http.Request) {
	if err := passThru(w, r); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, "Compile server error.")
	}
}

func passThru(w io.Writer, req *http.Request) error {
	log.Debug("passThru()")

	defer req.Body.Close()

	jsonReader, err := makeBodyJson(req.Body)
	if err != nil {
		log.Errorf("make body json error: %v", err)
		return err
	}

	r, err := http.Post(s.sandbox.URL, req.Header.Get("Content-type"), jsonReader)
	if err != nil {
		log.Errorf("making POST request: %v", err)
		return err
	}
	defer r.Body.Close()
	if _, err := io.Copy(w, r.Body); err != nil {
		log.Errorf("copying response Body: %v", err)
		return err
	}
	return nil
}

func makeBodyJson(httpBody io.Reader) (io.Reader, error) {
	// io.Reader -> []byte
	httpBodyByte, err := ioutil.ReadAll(httpBody)
	if err != nil {
		log.Errorf("body read error: %v", err)
		return nil, err
	}

	// []byte -> url.Values
	v, err := url.ParseQuery(string(httpBodyByte))
	if err != nil {
		log.Errorf("query parse error: %v", err)
		return nil, err
	}

	// make json
	json, err := json.Marshal(map[string]string{"Body": v.Get("body")})
	if err != nil {
		log.Errorf("json marshal error: %v", err)
		return nil, err
	}

	// convert byte json -> io.Reader
	return bytes.NewReader(json), nil
}
<commit_msg>rename Json -> JSON<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	log "github.com/Sirupsen/logrus"
)

func init() {
	http.HandleFunc("/compile", compile)
}

func compile(w http.ResponseWriter, r *http.Request) {
	if err := passThru(w, r); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Fprintln(w, "Compile server error.")
	}
}

func passThru(w io.Writer, req *http.Request) error {
	log.Debug("passThru()")

	defer req.Body.Close()

	jsonReader, err := makeBodyJSON(req.Body)
	if err != nil {
		log.Errorf("make body json error: %v", err)
		return err
	}

	r, err := http.Post(s.sandbox.URL, req.Header.Get("Content-type"), jsonReader)
	if err != nil {
		log.Errorf("making POST request: %v", err)
		return err
	}
	defer r.Body.Close()
	if _, err := io.Copy(w, r.Body); err != nil {
		log.Errorf("copying response Body: %v", err)
		return err
	}
	return nil
}

func makeBodyJSON(httpBody io.Reader) (io.Reader, error) {
	// io.Reader -> []byte
	httpBodyByte, err := ioutil.ReadAll(httpBody)
	if err != nil {
		log.Errorf("body read error: %v", err)
		return nil, err
	}

	// []byte -> url.Values
	v, err := url.ParseQuery(string(httpBodyByte))
	if err != nil {
		log.Errorf("query parse error: %v", err)
		return nil, err
	}

	// make json
	json, err := json.Marshal(map[string]string{"Body": v.Get("body")})
	if err != nil {
		log.Errorf("json marshal error: %v", err)
		return nil, err
	}

	// convert byte json -> io.Reader
	return bytes.NewReader(json), nil
}
<|endoftext|>


<commit_before>package astilectron

import (
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"

	"github.com/asticode/go-astilog"
	"github.com/asticode/go-astitools/context"
	"github.com/asticode/go-astitools/exec"
	"github.com/pkg/errors"
)

// Versions
const (
	DefaultAcceptTCPTimeout = 30 * time.Second
	VersionAstilectron      = "0.29.0"
	VersionElectron         = "1.8.1"
)

// Misc vars
var (
	validOSes = map[string]bool{
		"darwin":  true,
		"linux":   true,
		"windows": true,
	}
)

// App event names
const (
	EventNameAppClose         = "app.close"
	EventNameAppCmdQuit       = "app.cmd.quit" // Sends an event to Electron to properly quit the app
	EventNameAppCmdStop       = "app.cmd.stop" // Cancel the context which results in exiting abruptly Electron's app
	EventNameAppCrash         = "app.crash"
	EventNameAppErrorAccept   = "app.error.accept"
	EventNameAppEventReady    = "app.event.ready"
	EventNameAppNoAccept      = "app.no.accept"
	EventNameAppTooManyAccept = "app.too.many.accept"
)

// Astilectron represents an object capable of interacting with Astilectron
type Astilectron struct {
	canceller    *asticontext.Canceller
	channelQuit  chan bool
	closeOnce    sync.Once
	dispatcher   *dispatcher
	displayPool  *displayPool
	dock         *Dock
	executer     Executer
	identifier   *identifier
	listener     net.Listener
	options      Options
	paths        *Paths
	provisioner  Provisioner
	reader       *reader
	stderrWriter *astiexec.StdWriter
	stdoutWriter *astiexec.StdWriter
	supported    *Supported
	writer       *writer
}

// Options represents Astilectron options
type Options struct {
	AcceptTCPTimeout   time.Duration
	AppName            string
	AppIconDarwinPath  string // Darwin systems requires a specific .icns file
	AppIconDefaultPath string
	BaseDirectoryPath  string
	DataDirectoryPath  string
	ElectronSwitches   []string
	SingleInstance     bool
}

// Supported represents Astilectron supported features
type Supported struct {
	Notification *bool `json:"notification"`
}

// New creates a new Astilectron instance
func New(o Options) (a *Astilectron, err error) {
	// Validate the OS
	if !IsValidOS(runtime.GOOS) {
		err = errors.Wrapf(err, "OS %s is invalid", runtime.GOOS)
		return
	}

	// Init
	a = &Astilectron{
		canceller:   asticontext.NewCanceller(),
		channelQuit: make(chan bool),
		dispatcher:  newDispatcher(),
		displayPool: newDisplayPool(),
		executer:    DefaultExecuter,
		identifier:  newIdentifier(),
		options:     o,
		provisioner: DefaultProvisioner,
	}

	// Set paths
	if a.paths, err = newPaths(runtime.GOOS, runtime.GOARCH, o); err != nil {
		err = errors.Wrap(err, "creating new paths failed")
		return
	}

	// Add default listeners
	a.On(EventNameAppCmdStop, func(e Event) (deleteListener bool) {
		a.Stop()
		return
	})
	a.On(EventNameDisplayEventAdded, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventMetricsChanged, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventRemoved, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	return
}

// IsValidOS validates the OS
func IsValidOS(os string) (ok bool) {
	_, ok = validOSes[os]
	return
}

// SetProvisioner sets the provisioner
func (a *Astilectron) SetProvisioner(p Provisioner) *Astilectron {
	a.provisioner = p
	return a
}

// SetExecuter sets the executer
func (a *Astilectron) SetExecuter(e Executer) *Astilectron {
	a.executer = e
	return a
}

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
	a.dispatcher.addListener(targetIDApp, eventName, l)
}

// Start starts Astilectron
func (a *Astilectron) Start() (err error) {
	// Log
	astilog.Debug("Starting...")

	// Provision
	if err = a.provision(); err != nil {
		return errors.Wrap(err, "provisioning failed")
	}

	// Unfortunately communicating with Electron through stdin/stdout doesn't work on Windows so all communications
	// will be done through TCP
	if err = a.listenTCP(); err != nil {
		return errors.Wrap(err, "listening failed")
	}

	// Execute
	if err = a.execute(); err != nil {
		err = errors.Wrap(err, "executing failed")
		return
	}
	return
}

// provision provisions Astilectron
func (a *Astilectron) provision() error {
	astilog.Debug("Provisioning...")
	var ctx, _ = a.canceller.NewContext()
	return a.provisioner.Provision(ctx, a.options.AppName, runtime.GOOS, runtime.GOARCH, *a.paths)
}

// listenTCP listens to the first TCP connection coming its way (this should be Astilectron)
func (a *Astilectron) listenTCP() (err error) {
	// Log
	astilog.Debug("Listening...")

	// Listen
	if a.listener, err = net.Listen("tcp", "127.0.0.1:"); err != nil {
		return errors.Wrap(err, "tcp net.Listen failed")
	}

	// Check a connection has been accepted quickly enough
	var chanAccepted = make(chan bool)
	go a.watchNoAccept(a.options.AcceptTCPTimeout, chanAccepted)

	// Accept connections
	go a.acceptTCP(chanAccepted)
	return
}

// watchNoAccept checks whether a TCP connection is accepted quickly enough
func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan bool) {
	//check timeout
	if timeout == 0 {
		timeout = DefaultAcceptTCPTimeout
	}
	var t = time.NewTimer(timeout)
	defer t.Stop()
	for {
		select {
		case <-chanAccepted:
			return
		case <-t.C:
			astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
			a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}
	}
}

// watchAcceptTCP accepts TCP connections
func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
	for i := 0; i <= 1; i++ {
		// Accept
		var conn net.Conn
		var err error
		if conn, err = a.listener.Accept(); err != nil {
			astilog.Errorf("%s while TCP accepting", err)
			a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}

		// We only accept the first connection which should be Astilectron, close the next one and stop
		// the app
		if i > 0 {
			astilog.Errorf("Too many TCP connections")
			a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			conn.Close()
			return
		}

		// Let the timer know a connection has been accepted
		chanAccepted <- true

		// Create reader and writer
		a.writer = newWriter(conn)
		ctx, _ := a.canceller.NewContext()
		a.reader = newReader(ctx, a.dispatcher, conn)
		go a.reader.read()
	}
}

// execute executes Astilectron in Electron
func (a *Astilectron) execute() (err error) {
	// Log
	astilog.Debug("Executing...")

	// Create command
	var ctx, _ = a.canceller.NewContext()
	var singleInstance string
	if a.options.SingleInstance == true {
		singleInstance = "true"
	} else {
		singleInstance = "false"
	}
	var cmd = exec.CommandContext(ctx, a.paths.AppExecutable(), append([]string{a.paths.AstilectronApplication(), a.listener.Addr().String(), singleInstance}, a.options.ElectronSwitches...)...)
	a.stderrWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stderr says: %s", i) })
	a.stdoutWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stdout says: %s", i) })
	cmd.Stderr = a.stderrWriter
	cmd.Stdout = a.stdoutWriter

	// Execute command
	if err = a.executeCmd(cmd); err != nil {
		return errors.Wrap(err, "executing cmd failed")
	}
	return
}

// executeCmd executes the command
func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
	var e = synchronousFunc(a.canceller, a, func() {
		err = a.executer(a, cmd)
	}, EventNameAppEventReady)

	// Update display pool
	if e.Displays != nil {
		a.displayPool.update(e.Displays)
	}

	// Create dock
	a.dock = newDock(a.canceller, a.dispatcher, a.identifier, a.writer)

	// Update supported features
	a.supported = e.Supported
	return
}

// watchCmd watches the cmd execution
func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
	// Wait
	cmd.Wait()

	// Check the canceller to check whether it was a crash
	if !a.canceller.Cancelled() {
		astilog.Debug("App has crashed")
		a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: targetIDApp})
	} else {
		astilog.Debug("App has closed")
		a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: targetIDApp})
	}
	a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
}

// Close closes Astilectron properly
func (a *Astilectron) Close() {
	astilog.Debug("Closing...")
	a.canceller.Cancel()
	if a.listener != nil {
		a.listener.Close()
	}
	if a.reader != nil {
		a.reader.close()
	}
	if a.stderrWriter != nil {
		a.stderrWriter.Close()
	}
	if a.stdoutWriter != nil {
		a.stdoutWriter.Close()
	}
	if a.writer != nil {
		a.writer.close()
	}
}

// HandleSignals handles signals
func (a *Astilectron) HandleSignals() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGABRT, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		for sig := range ch {
			astilog.Debugf("Received signal %s", sig)
			a.Stop()
		}
	}()
}

// Stop orders Astilectron to stop
func (a *Astilectron) Stop() {
	astilog.Debug("Stopping...")
	a.canceller.Cancel()
	a.closeOnce.Do(func() {
		close(a.channelQuit)
	})
}

// Wait is a blocking pattern
func (a *Astilectron) Wait() {
	<-a.channelQuit
}

// Quit quits the app
func (a *Astilectron) Quit() error {
	return a.writer.write(Event{Name: EventNameAppCmdQuit})
}

// Paths returns the paths
func (a *Astilectron) Paths() Paths {
	return *a.paths
}

// Displays returns the displays
func (a *Astilectron) Displays() []*Display {
	return a.displayPool.all()
}

// Dock returns the dock
func (a *Astilectron) Dock() *Dock {
	return a.dock
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
	return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
	return newMenu(nil, targetIDApp, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
func (a *Astilectron) NewWindow(url string, o *WindowOptions) (*Window, error) {
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindowInDisplay creates a new window in a specific display
// This overrides the center attribute
func (a *Astilectron) NewWindowInDisplay(d *Display, url string, o *WindowOptions) (*Window, error) {
	if o.X != nil {
		*o.X += d.Bounds().X
	} else {
		o.X = PtrInt(d.Bounds().X)
	}
	if o.Y != nil {
		*o.Y += d.Bounds().Y
	} else {
		o.Y = PtrInt(d.Bounds().Y)
	}
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewTray creates a new tray
func (a *Astilectron) NewTray(o *TrayOptions) *Tray {
	return newTray(o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewNotification creates a new notification
func (a *Astilectron) NewNotification(o *NotificationOptions) *Notification {
	return newNotification(o, a.supported != nil && a.supported.Notification != nil && *a.supported.Notification, a.canceller, a.dispatcher, a.identifier, a.writer)
}
<commit_msg>Bumped Electron version to 4.0.1<commit_after>package astilectron

import (
	"net"
	"os"
	"os/exec"
	"os/signal"
	"runtime"
	"sync"
	"syscall"
	"time"

	"github.com/asticode/go-astilog"
	"github.com/asticode/go-astitools/context"
	"github.com/asticode/go-astitools/exec"
	"github.com/pkg/errors"
)

// Versions
const (
	DefaultAcceptTCPTimeout = 30 * time.Second
	VersionAstilectron      = "0.29.0"
	VersionElectron         = "4.0.1"
)

// Misc vars
var (
	validOSes = map[string]bool{
		"darwin":  true,
		"linux":   true,
		"windows": true,
	}
)

// App event names
const (
	EventNameAppClose         = "app.close"
	EventNameAppCmdQuit       = "app.cmd.quit" // Sends an event to Electron to properly quit the app
	EventNameAppCmdStop       = "app.cmd.stop" // Cancel the context which results in exiting abruptly Electron's app
	EventNameAppCrash         = "app.crash"
	EventNameAppErrorAccept   = "app.error.accept"
	EventNameAppEventReady    = "app.event.ready"
	EventNameAppNoAccept      = "app.no.accept"
	EventNameAppTooManyAccept = "app.too.many.accept"
)

// Astilectron represents an object capable of interacting with Astilectron
type Astilectron struct {
	canceller    *asticontext.Canceller
	channelQuit  chan bool
	closeOnce    sync.Once
	dispatcher   *dispatcher
	displayPool  *displayPool
	dock         *Dock
	executer     Executer
	identifier   *identifier
	listener     net.Listener
	options      Options
	paths        *Paths
	provisioner  Provisioner
	reader       *reader
	stderrWriter *astiexec.StdWriter
	stdoutWriter *astiexec.StdWriter
	supported    *Supported
	writer       *writer
}

// Options represents Astilectron options
type Options struct {
	AcceptTCPTimeout   time.Duration
	AppName            string
	AppIconDarwinPath  string // Darwin systems requires a specific .icns file
	AppIconDefaultPath string
	BaseDirectoryPath  string
	DataDirectoryPath  string
	ElectronSwitches   []string
	SingleInstance     bool
}

// Supported represents Astilectron supported features
type Supported struct {
	Notification *bool `json:"notification"`
}

// New creates a new Astilectron instance
func New(o Options) (a *Astilectron, err error) {
	// Validate the OS
	if !IsValidOS(runtime.GOOS) {
		err = errors.Wrapf(err, "OS %s is invalid", runtime.GOOS)
		return
	}

	// Init
	a = &Astilectron{
		canceller:   asticontext.NewCanceller(),
		channelQuit: make(chan bool),
		dispatcher:  newDispatcher(),
		displayPool: newDisplayPool(),
		executer:    DefaultExecuter,
		identifier:  newIdentifier(),
		options:     o,
		provisioner: DefaultProvisioner,
	}

	// Set paths
	if a.paths, err = newPaths(runtime.GOOS, runtime.GOARCH, o); err != nil {
		err = errors.Wrap(err, "creating new paths failed")
		return
	}

	// Add default listeners
	a.On(EventNameAppCmdStop, func(e Event) (deleteListener bool) {
		a.Stop()
		return
	})
	a.On(EventNameDisplayEventAdded, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventMetricsChanged, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	a.On(EventNameDisplayEventRemoved, func(e Event) (deleteListener bool) {
		a.displayPool.update(e.Displays)
		return
	})
	return
}

// IsValidOS validates the OS
func IsValidOS(os string) (ok bool) {
	_, ok = validOSes[os]
	return
}

// SetProvisioner sets the provisioner
func (a *Astilectron) SetProvisioner(p Provisioner) *Astilectron {
	a.provisioner = p
	return a
}

// SetExecuter sets the executer
func (a *Astilectron) SetExecuter(e Executer) *Astilectron {
	a.executer = e
	return a
}

// On implements the Listenable interface
func (a *Astilectron) On(eventName string, l Listener) {
	a.dispatcher.addListener(targetIDApp, eventName, l)
}

// Start starts Astilectron
func (a *Astilectron) Start() (err error) {
	// Log
	astilog.Debug("Starting...")

	// Provision
	if err = a.provision(); err != nil {
		return errors.Wrap(err, "provisioning failed")
	}

	// Unfortunately communicating with Electron through stdin/stdout doesn't work on Windows so all communications
	// will be done through TCP
	if err = a.listenTCP(); err != nil {
		return errors.Wrap(err, "listening failed")
	}

	// Execute
	if err = a.execute(); err != nil {
		err = errors.Wrap(err, "executing failed")
		return
	}
	return
}

// provision provisions Astilectron
func (a *Astilectron) provision() error {
	astilog.Debug("Provisioning...")
	var ctx, _ = a.canceller.NewContext()
	return a.provisioner.Provision(ctx, a.options.AppName, runtime.GOOS, runtime.GOARCH, *a.paths)
}

// listenTCP listens to the first TCP connection coming its way (this should be Astilectron)
func (a *Astilectron) listenTCP() (err error) {
	// Log
	astilog.Debug("Listening...")

	// Listen
	if a.listener, err = net.Listen("tcp", "127.0.0.1:"); err != nil {
		return errors.Wrap(err, "tcp net.Listen failed")
	}

	// Check a connection has been accepted quickly enough
	var chanAccepted = make(chan bool)
	go a.watchNoAccept(a.options.AcceptTCPTimeout, chanAccepted)

	// Accept connections
	go a.acceptTCP(chanAccepted)
	return
}

// watchNoAccept checks whether a TCP connection is accepted quickly enough
func (a *Astilectron) watchNoAccept(timeout time.Duration, chanAccepted chan bool) {
	//check timeout
	if timeout == 0 {
		timeout = DefaultAcceptTCPTimeout
	}
	var t = time.NewTimer(timeout)
	defer t.Stop()
	for {
		select {
		case <-chanAccepted:
			return
		case <-t.C:
			astilog.Errorf("No TCP connection has been accepted in the past %s", timeout)
			a.dispatcher.dispatch(Event{Name: EventNameAppNoAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}
	}
}

// watchAcceptTCP accepts TCP connections
func (a *Astilectron) acceptTCP(chanAccepted chan bool) {
	for i := 0; i <= 1; i++ {
		// Accept
		var conn net.Conn
		var err error
		if conn, err = a.listener.Accept(); err != nil {
			astilog.Errorf("%s while TCP accepting", err)
			a.dispatcher.dispatch(Event{Name: EventNameAppErrorAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			return
		}

		// We only accept the first connection which should be Astilectron, close the next one and stop
		// the app
		if i > 0 {
			astilog.Errorf("Too many TCP connections")
			a.dispatcher.dispatch(Event{Name: EventNameAppTooManyAccept, TargetID: targetIDApp})
			a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
			conn.Close()
			return
		}

		// Let the timer know a connection has been accepted
		chanAccepted <- true

		// Create reader and writer
		a.writer = newWriter(conn)
		ctx, _ := a.canceller.NewContext()
		a.reader = newReader(ctx, a.dispatcher, conn)
		go a.reader.read()
	}
}

// execute executes Astilectron in Electron
func (a *Astilectron) execute() (err error) {
	// Log
	astilog.Debug("Executing...")

	// Create command
	var ctx, _ = a.canceller.NewContext()
	var singleInstance string
	if a.options.SingleInstance == true {
		singleInstance = "true"
	} else {
		singleInstance = "false"
	}
	var cmd = exec.CommandContext(ctx, a.paths.AppExecutable(), append([]string{a.paths.AstilectronApplication(), a.listener.Addr().String(), singleInstance}, a.options.ElectronSwitches...)...)
	a.stderrWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stderr says: %s", i) })
	a.stdoutWriter = astiexec.NewStdWriter(func(i []byte) { astilog.Debugf("Stdout says: %s", i) })
	cmd.Stderr = a.stderrWriter
	cmd.Stdout = a.stdoutWriter

	// Execute command
	if err = a.executeCmd(cmd); err != nil {
		return errors.Wrap(err, "executing cmd failed")
	}
	return
}

// executeCmd executes the command
func (a *Astilectron) executeCmd(cmd *exec.Cmd) (err error) {
	var e = synchronousFunc(a.canceller, a, func() {
		err = a.executer(a, cmd)
	}, EventNameAppEventReady)

	// Update display pool
	if e.Displays != nil {
		a.displayPool.update(e.Displays)
	}

	// Create dock
	a.dock = newDock(a.canceller, a.dispatcher, a.identifier, a.writer)

	// Update supported features
	a.supported = e.Supported
	return
}

// watchCmd watches the cmd execution
func (a *Astilectron) watchCmd(cmd *exec.Cmd) {
	// Wait
	cmd.Wait()

	// Check the canceller to check whether it was a crash
	if !a.canceller.Cancelled() {
		astilog.Debug("App has crashed")
		a.dispatcher.dispatch(Event{Name: EventNameAppCrash, TargetID: targetIDApp})
	} else {
		astilog.Debug("App has closed")
		a.dispatcher.dispatch(Event{Name: EventNameAppClose, TargetID: targetIDApp})
	}
	a.dispatcher.dispatch(Event{Name: EventNameAppCmdStop, TargetID: targetIDApp})
}

// Close closes Astilectron properly
func (a *Astilectron) Close() {
	astilog.Debug("Closing...")
	a.canceller.Cancel()
	if a.listener != nil {
		a.listener.Close()
	}
	if a.reader != nil {
		a.reader.close()
	}
	if a.stderrWriter != nil {
		a.stderrWriter.Close()
	}
	if a.stdoutWriter != nil {
		a.stdoutWriter.Close()
	}
	if a.writer != nil {
		a.writer.close()
	}
}

// HandleSignals handles signals
func (a *Astilectron) HandleSignals() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGABRT, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		for sig := range ch {
			astilog.Debugf("Received signal %s", sig)
			a.Stop()
		}
	}()
}

// Stop orders Astilectron to stop
func (a *Astilectron) Stop() {
	astilog.Debug("Stopping...")
	a.canceller.Cancel()
	a.closeOnce.Do(func() {
		close(a.channelQuit)
	})
}

// Wait is a blocking pattern
func (a *Astilectron) Wait() {
	<-a.channelQuit
}

// Quit quits the app
func (a *Astilectron) Quit() error {
	return a.writer.write(Event{Name: EventNameAppCmdQuit})
}

// Paths returns the paths
func (a *Astilectron) Paths() Paths {
	return *a.paths
}

// Displays returns the displays
func (a *Astilectron) Displays() []*Display {
	return a.displayPool.all()
}

// Dock returns the dock
func (a *Astilectron) Dock() *Dock {
	return a.dock
}

// PrimaryDisplay returns the primary display
func (a *Astilectron) PrimaryDisplay() *Display {
	return a.displayPool.primary()
}

// NewMenu creates a new app menu
func (a *Astilectron) NewMenu(i []*MenuItemOptions) *Menu {
	return newMenu(nil, targetIDApp, i, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindow creates a new window
func (a *Astilectron) NewWindow(url string, o *WindowOptions) (*Window, error) {
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewWindowInDisplay creates a new window in a specific display
// This overrides the center attribute
func (a *Astilectron) NewWindowInDisplay(d *Display, url string, o *WindowOptions) (*Window, error) {
	if o.X != nil {
		*o.X += d.Bounds().X
	} else {
		o.X = PtrInt(d.Bounds().X)
	}
	if o.Y != nil {
		*o.Y += d.Bounds().Y
	} else {
		o.Y = PtrInt(d.Bounds().Y)
	}
	return newWindow(a.options, a.Paths(), url, o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewTray creates a new tray
func (a *Astilectron) NewTray(o *TrayOptions) *Tray {
	return newTray(o, a.canceller, a.dispatcher, a.identifier, a.writer)
}

// NewNotification creates a new notification
func (a *Astilectron) NewNotification(o *NotificationOptions) *Notification {
	return newNotification(o, a.supported != nil && a.supported.Notification != nil && *a.supported.Notification, a.canceller, a.dispatcher, a.identifier, a.writer)
}
<|endoftext|>


<commit_before>package cdsclient

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"regexp"
	"runtime/pprof"
	"strings"
	"time"

	"github.com/ovh/cds/cli"
	"github.com/ovh/cds/sdk/telemetry"

	"github.com/dgrijalva/jwt-go"

	"github.com/ovh/cds/sdk"
)

const (
	// RequestedIfModifiedSinceHeader is used as HTTP header
	RequestedIfModifiedSinceHeader = "If-Modified-Since"
	// ResponseAPITimeHeader is used as HTTP header
	ResponseAPITimeHeader = "X-Api-Time"
	// ResponseAPINanosecondsTimeHeader is used as HTTP header
	ResponseAPINanosecondsTimeHeader = "X-Api-Nanoseconds-Time"
	// ResponseEtagHeader is used as HTTP header
	ResponseEtagHeader = "Etag"
	// ResponseProcessTimeHeader is used as HTTP header
	ResponseProcessTimeHeader = "X-Api-Process-Time"
)

// RequestModifier is used to modify behavior of Request and Steam functions
type RequestModifier func(req *http.Request)

// HTTPClient is a interface for HTTPClient mock
type HTTPClient interface {
	Do(*http.Request) (*http.Response, error)
}

// SetHeader modify headers of http.Request
func SetHeader(key, value string) RequestModifier {
	return func(req *http.Request) {
		req.Header.Set(key, value)
	}
}

// WithQueryParameter add query parameters to your http.Request
func WithQueryParameter(key, value string) RequestModifier {
	return func(req *http.Request) {
		q := req.URL.Query()
		q.Set(key, value)
		req.URL.RawQuery = q.Encode()
	}
}

// PostJSON post the *in* struct as json. If set, it unmarshalls the response to *out*
func (c *client) PostJSON(ctx context.Context, path string, in interface{}, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodPost, path, in, out, mods...)
	return code, err
}

// PostJSON ut the *in* struct as json. If set, it unmarshalls the response to *out*
func (c *client) PutJSON(ctx context.Context, path string, in interface{}, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodPut, path, in, out, mods...)
	return code, err
}

// GetJSON get the requested path If set, it unmarshalls the response to *out*
func (c *client) GetJSON(ctx context.Context, path string, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodGet, path, nil, out, mods...)
	return code, err
}

// GetJSONWithHeaders get the requested path If set, it unmarshalls the response to *out* and return response headers
func (c *client) GetJSONWithHeaders(path string, out interface{}, mods ...RequestModifier) (http.Header, int, error) {
	_, header, code, err := c.RequestJSON(context.Background(), http.MethodGet, path, nil, out, mods...)
	return header, code, err
}

// DeleteJSON deletes the requested path If set, it unmarshalls the response to *out*
func (c *client) DeleteJSON(ctx context.Context, path string, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodDelete, path, nil, out, mods...)
	return code, err
}

// RequestJSON does a request with the *in* struct as json. If set, it unmarshalls the response to *out*
func (c *client) RequestJSON(ctx context.Context, method, path string, in interface{}, out interface{}, mods ...RequestModifier) ([]byte, http.Header, int, error) {
	var b = []byte{}
	var err error

	if in != nil {
		b, err = json.Marshal(in)
		if err != nil {
			return nil, nil, 0, sdk.WithStack(err)
		}
	}

	var body io.Reader
	if len(b) > 0 {
		body = bytes.NewBuffer(b)
	}

	res, header, code, err := c.Request(ctx, method, path, body, mods...)
	if err != nil {
		return nil, nil, code, sdk.WithStack(err)
	}

	if code >= 400 {
		if err := sdk.DecodeError(res); err != nil {
			return res, nil, code, err
		}
		return res, nil, code, sdk.WithStack(fmt.Errorf("HTTP %d", code))
	}

	if code == 204 {
		return res, header, code, nil
	}

	if out != nil {
		if err := json.Unmarshal(res, out); err != nil {
			return res, nil, code, sdk.WithStack(err)
		}
	}

	return res, header, code, nil
}

// Request executes an authentificated HTTP request on $path given $method and $args
func (c *client) Request(ctx context.Context, method string, path string, body io.Reader, mods ...RequestModifier) ([]byte, http.Header, int, error) {
	respBody, respHeader, code, err := c.Stream(ctx, method, path, body, false, mods...)
	if err != nil {
		return nil, nil, 0, sdk.WithStack(err)
	}
	defer func() {
		// Drain and close the body to let the Transport reuse the connection
		_, _ = io.Copy(ioutil.Discard, respBody)
		_ = respBody.Close()
	}()

	var bodyBtes []byte
	bodyBtes, err = ioutil.ReadAll(respBody)
	if err != nil {
		return nil, nil, code, sdk.WithStack(err)
	}

	if c.config.Verbose {
		if len(bodyBtes) > 0 {
			log.Printf("Response Body: %s\n", bodyBtes)
		}
	}

	if code >= 400 {
		if err := sdk.DecodeError(bodyBtes); err != nil {
			return bodyBtes, nil, code, sdk.WithStack(err)
		}
		return bodyBtes, nil, code, sdk.WithStack(fmt.Errorf("HTTP %d", code))
	}

	return bodyBtes, respHeader, code, nil
}

// signin route pattern

var signinRouteRegexp = regexp.MustCompile(`\/auth\/consumer\/.*\/signin`)

func extractBodyErrorFromResponse(r *http.Response) error {
	body, _ := ioutil.ReadAll(r.Body)
	r.Body.Close() // nolint
	if err := sdk.DecodeError(body); err != nil {
		return sdk.WithStack(err)
	}
	return sdk.WithStack(fmt.Errorf("HTTP %d", r.StatusCode))
}

// Stream makes an authenticated http request and return io.ReadCloser
func (c *client) Stream(ctx context.Context, method string, path string, body io.Reader, noTimeout bool, mods ...RequestModifier) (io.ReadCloser, http.Header, int, error) {
	// Checks that current session_token is still valid
	// If not, challenge a new one against the authenticationToken
	var checkToken = !strings.Contains(path, "/auth/consumer/builtin/signin") &&
		!strings.Contains(path, "/auth/consumer/local/signin") &&
		!strings.Contains(path, "/auth/consumer/local/signup") &&
		!strings.Contains(path, "/auth/consumer/local/verify") &&
		!strings.Contains(path, "/auth/consumer/worker/signin")

	if checkToken && !c.config.HasValidSessionToken() && c.config.BuitinConsumerAuthenticationToken != "" {
		if c.config.Verbose {
			log.Printf("session token invalid: (%s). Relogin...\n", c.config.SessionToken)
		}
		resp, err := c.AuthConsumerSignin(sdk.ConsumerBuiltin, sdk.AuthConsumerSigninRequest{"token": c.config.BuitinConsumerAuthenticationToken})
		if err != nil {
			return nil, nil, -1, sdk.WithStack(err)
		}
		if c.config.Verbose {
			log.Println("jwt: ", sdk.StringFirstN(resp.Token, 12))
		}
		c.config.SessionToken = resp.Token
	}

	labels := pprof.Labels("path", path, "method", method)
	ctx = pprof.WithLabels(ctx, labels)
	pprof.SetGoroutineLabels(ctx)

	// In case where the given reader is not a ReadSeeker we should store the body in ram to retry http request
	var bodyBytes []byte
	var err error
	if _, ok := body.(io.ReadSeeker); !ok && body != nil {
		bodyBytes, err = ioutil.ReadAll(body)
		if err != nil {
			return nil, nil, 0, sdk.WithStack(err)
		}
	}

	var url string
	if strings.HasPrefix(path, "http") {
		url = path
	} else {
		url = c.config.Host + path
	}

	var savederror error
	for i := 0; i <= c.config.Retry; i++ {
		var req *http.Request
		var requestError error
		if rs, ok := body.(io.ReadSeeker); ok {
			if _, err := rs.Seek(0, 0); err != nil {
				return nil, nil, 0, sdk.WrapError(err, "request failed after %d retries", i)
			}
			req, requestError = http.NewRequest(method, url, body)
		} else {
			req, requestError = http.NewRequest(method, url, bytes.NewBuffer(bodyBytes))
		}
		if requestError != nil {
			savederror = sdk.WithStack(requestError)
			continue
		}

		req = req.WithContext(ctx)
		date := sdk.FormatDateRFC5322(time.Now())
		req.Header.Set("Date", date)
		req.Header.Set("X-CDS-RemoteTime", date)

		if c.config.Verbose {
			log.Printf("Stream > context> %s\n", telemetry.DumpContext(ctx))
		}
		spanCtx, ok := telemetry.ContextToSpanContext(ctx)
		if ok {
			telemetry.DefaultFormat.SpanContextToRequest(spanCtx, req)
		}

		for i := range mods {
			if mods[i] != nil {
				mods[i](req)
			}
		}

		if req.Header.Get("Content-Type") == "" {
			req.Header.Set("Content-Type", "application/json")
		}

		req.Header.Set("Connection", "close")

		//No auth on signing routes or on url that is not cds configured in config.Host
		if strings.HasPrefix(url, c.config.Host) && !signinRouteRegexp.MatchString(path) {
			if _, _, err := new(jwt.Parser).ParseUnverified(c.config.SessionToken, &sdk.AuthSessionJWTClaims{}); err == nil {
				if c.config.Verbose {
					log.Println("JWT recognized")
				}
				auth := "Bearer " + c.config.SessionToken
				req.Header.Add("Authorization", auth)
			}
		}

		if c.config.Verbose {
			log.Println(cli.Green("********REQUEST**********"))
			dmp, _ := httputil.DumpRequestOut(req, true)
			log.Printf("%s", string(dmp))
			log.Println(cli.Green("**************************"))
		}

		var errDo error
		var resp *http.Response
		if noTimeout {
			resp, errDo = c.httpSSEClient.Do(req)
		} else {
			resp, errDo = c.httpClient.Do(req)
		}
		if errDo != nil {
			savederror = sdk.WithStack(errDo)
			continue
		}

		if c.config.Verbose {
			log.Println(cli.Yellow("********RESPONSE**********"))
			dmp, _ := httputil.DumpResponse(resp, true)
			log.Printf("%s", string(dmp))
			log.Println(cli.Yellow("**************************"))
		}

		if resp.StatusCode == 401 {
			c.config.SessionToken = ""
		}

		if resp.StatusCode == 409 || resp.StatusCode > 500 {
			time.Sleep(250 * time.Millisecond)
			savederror = extractBodyErrorFromResponse(resp)
			continue
		}

		// if no request error by status > 500, check CDS error
		// if there is a CDS errors, return it
		if resp.StatusCode == 500 {
			return nil, resp.Header, resp.StatusCode, extractBodyErrorFromResponse(resp)
		}

		return resp.Body, resp.Header, resp.StatusCode, nil
	}

	return nil, nil, 0, sdk.WrapError(savederror, "request failed after %d retries", c.config.Retry)
}

// UploadMultiPart upload multipart
func (c *client) UploadMultiPart(method string, path string, body *bytes.Buffer, mods ...RequestModifier) ([]byte, int, error) {
	// Checks that current session_token is still valid
	// If not, challenge a new one against the authenticationToken
	if !c.config.HasValidSessionToken() && c.config.BuitinConsumerAuthenticationToken != "" {
		resp, err := c.AuthConsumerSignin(sdk.ConsumerBuiltin, sdk.AuthConsumerSigninRequest{"token": c.config.BuitinConsumerAuthenticationToken})
		if err != nil {
			return nil, -1, err
		}
		c.config.SessionToken = resp.Token
	}

	var req *http.Request
	req, errRequest := http.NewRequest(method, c.config.Host+path, body)
	if errRequest != nil {
		return nil, 0, errRequest
	}

	req.Header.Set("Content-Type", "application/octet-stream")
	req.Header.Set("Connection", "close")

	for i := range mods {
		mods[i](req)
	}

	//No auth on signing routes
	if !signinRouteRegexp.MatchString(path) {
		if _, _, err := new(jwt.Parser).ParseUnverified(c.config.SessionToken, &sdk.AuthSessionJWTClaims{}); err == nil {
			if c.config.Verbose {
				fmt.Println("JWT recognized")
			}
			auth := "Bearer " + c.config.SessionToken
			req.Header.Add("Authorization", auth)
		}
	}

	resp, err := c.httpSSEClient.Do(req)
	if err != nil {
		return nil, 0, err
	}
	defer resp.Body.Close()

	if c.config.Verbose {
		fmt.Printf("Response Status: %s\n", resp.Status)
		fmt.Printf("Request path: %s\n", c.config.Host+path)
		fmt.Printf("Request Headers: %s\n", req.Header)
		fmt.Printf("Response Headers: %s\n", resp.Header)
	}

	if resp.StatusCode == 401 {
		c.config.SessionToken = ""
	}

	var respBody []byte
	respBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, resp.StatusCode, err
	}

	if c.config.Verbose {
		if len(body.Bytes()) > 0 {
			fmt.Printf("Response Body: %s\n", body.String())
		}
	}

	return respBody, resp.StatusCode, nil
}
<commit_msg>fix(sdk): stream should return original error (#5652)<commit_after>package cdsclient

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"regexp"
	"runtime/pprof"
	"strings"
	"time"

	"github.com/ovh/cds/cli"
	"github.com/ovh/cds/sdk/telemetry"

	"github.com/dgrijalva/jwt-go"

	"github.com/ovh/cds/sdk"
)

const (
	// RequestedIfModifiedSinceHeader is used as HTTP header
	RequestedIfModifiedSinceHeader = "If-Modified-Since"
	// ResponseAPITimeHeader is used as HTTP header
	ResponseAPITimeHeader = "X-Api-Time"
	// ResponseAPINanosecondsTimeHeader is used as HTTP header
	ResponseAPINanosecondsTimeHeader = "X-Api-Nanoseconds-Time"
	// ResponseEtagHeader is used as HTTP header
	ResponseEtagHeader = "Etag"
	// ResponseProcessTimeHeader is used as HTTP header
	ResponseProcessTimeHeader = "X-Api-Process-Time"
)

// RequestModifier is used to modify behavior of Request and Steam functions
type RequestModifier func(req *http.Request)

// HTTPClient is a interface for HTTPClient mock
type HTTPClient interface {
	Do(*http.Request) (*http.Response, error)
}

// SetHeader modify headers of http.Request
func SetHeader(key, value string) RequestModifier {
	return func(req *http.Request) {
		req.Header.Set(key, value)
	}
}

// WithQueryParameter add query parameters to your http.Request
func WithQueryParameter(key, value string) RequestModifier {
	return func(req *http.Request) {
		q := req.URL.Query()
		q.Set(key, value)
		req.URL.RawQuery = q.Encode()
	}
}

// PostJSON post the *in* struct as json. If set, it unmarshalls the response to *out*
func (c *client) PostJSON(ctx context.Context, path string, in interface{}, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodPost, path, in, out, mods...)
	return code, err
}

// PostJSON ut the *in* struct as json. If set, it unmarshalls the response to *out*
func (c *client) PutJSON(ctx context.Context, path string, in interface{}, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodPut, path, in, out, mods...)
	return code, err
}

// GetJSON get the requested path If set, it unmarshalls the response to *out*
func (c *client) GetJSON(ctx context.Context, path string, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodGet, path, nil, out, mods...)
	return code, err
}

// GetJSONWithHeaders get the requested path If set, it unmarshalls the response to *out* and return response headers
func (c *client) GetJSONWithHeaders(path string, out interface{}, mods ...RequestModifier) (http.Header, int, error) {
	_, header, code, err := c.RequestJSON(context.Background(), http.MethodGet, path, nil, out, mods...)
	return header, code, err
}

// DeleteJSON deletes the requested path If set, it unmarshalls the response to *out*
func (c *client) DeleteJSON(ctx context.Context, path string, out interface{}, mods ...RequestModifier) (int, error) {
	_, _, code, err := c.RequestJSON(ctx, http.MethodDelete, path, nil, out, mods...)
	return code, err
}

// RequestJSON does a request with the *in* struct as json. If set, it unmarshalls the response to *out*
func (c *client) RequestJSON(ctx context.Context, method, path string, in interface{}, out interface{}, mods ...RequestModifier) ([]byte, http.Header, int, error) {
	var b = []byte{}
	var err error

	if in != nil {
		b, err = json.Marshal(in)
		if err != nil {
			return nil, nil, 0, sdk.WithStack(err)
		}
	}

	var body io.Reader
	if len(b) > 0 {
		body = bytes.NewBuffer(b)
	}

	res, header, code, err := c.Request(ctx, method, path, body, mods...)
	if err != nil {
		return nil, nil, code, sdk.WithStack(err)
	}

	if code >= 400 {
		if err := sdk.DecodeError(res); err != nil {
			return res, nil, code, err
		}
		return res, nil, code, sdk.WithStack(fmt.Errorf("HTTP %d", code))
	}

	if code == 204 {
		return res, header, code, nil
	}

	if out != nil {
		if err := json.Unmarshal(res, out); err != nil {
			return res, nil, code, sdk.WithStack(err)
		}
	}

	return res, header, code, nil
}

// Request executes an authentificated HTTP request on $path given $method and $args
func (c *client) Request(ctx context.Context, method string, path string, body io.Reader, mods ...RequestModifier) ([]byte, http.Header, int, error) {
	respBody, respHeader, code, err := c.Stream(ctx, method, path, body, false, mods...)
	if err != nil {
		return nil, nil, 0, sdk.WithStack(err)
	}
	defer func() {
		// Drain and close the body to let the Transport reuse the connection
		_, _ = io.Copy(ioutil.Discard, respBody)
		_ = respBody.Close()
	}()

	var bodyBtes []byte
	bodyBtes, err = ioutil.ReadAll(respBody)
	if err != nil {
		return nil, nil, code, sdk.WithStack(err)
	}

	if c.config.Verbose {
		if len(bodyBtes) > 0 {
			log.Printf("Response Body: %s\n", bodyBtes)
		}
	}

	if code >= 400 {
		if err := sdk.DecodeError(bodyBtes); err != nil {
			return bodyBtes, nil, code, sdk.WithStack(err)
		}
		return bodyBtes, nil, code, sdk.WithStack(fmt.Errorf("HTTP %d", code))
	}

	return bodyBtes, respHeader, code, nil
}

// signin route pattern

var signinRouteRegexp = regexp.MustCompile(`\/auth\/consumer\/.*\/signin`)

func extractBodyErrorFromResponse(r *http.Response) error {
	body, _ := ioutil.ReadAll(r.Body)
	r.Body.Close() // nolint
	if err := sdk.DecodeError(body); err != nil {
		return sdk.WithStack(err)
	}
	return sdk.WithStack(fmt.Errorf("HTTP %d", r.StatusCode))
}

// Stream makes an authenticated http request and return io.ReadCloser
func (c *client) Stream(ctx context.Context, method string, path string, body io.Reader, noTimeout bool, mods ...RequestModifier) (io.ReadCloser, http.Header, int, error) {
	// Checks that current session_token is still valid
	// If not, challenge a new one against the authenticationToken
	var checkToken = !strings.Contains(path, "/auth/consumer/builtin/signin") &&
		!strings.Contains(path, "/auth/consumer/local/signin") &&
		!strings.Contains(path, "/auth/consumer/local/signup") &&
		!strings.Contains(path, "/auth/consumer/local/verify") &&
		!strings.Contains(path, "/auth/consumer/worker/signin")

	if checkToken && !c.config.HasValidSessionToken() && c.config.BuitinConsumerAuthenticationToken != "" {
		if c.config.Verbose {
			log.Printf("session token invalid: (%s). Relogin...\n", c.config.SessionToken)
		}
		resp, err := c.AuthConsumerSignin(sdk.ConsumerBuiltin, sdk.AuthConsumerSigninRequest{"token": c.config.BuitinConsumerAuthenticationToken})
		if err != nil {
			return nil, nil, -1, sdk.WithStack(err)
		}
		if c.config.Verbose {
			log.Println("jwt: ", sdk.StringFirstN(resp.Token, 12))
		}
		c.config.SessionToken = resp.Token
	}

	labels := pprof.Labels("path", path, "method", method)
	ctx = pprof.WithLabels(ctx, labels)
	pprof.SetGoroutineLabels(ctx)

	// In case where the given reader is not a ReadSeeker we should store the body in ram to retry http request
	var bodyBytes []byte
	var err error
	if _, ok := body.(io.ReadSeeker); !ok && body != nil {
		bodyBytes, err = ioutil.ReadAll(body)
		if err != nil {
			return nil, nil, 0, sdk.WithStack(err)
		}
	}

	var url string
	if strings.HasPrefix(path, "http") {
		url = path
	} else {
		url = c.config.Host + path
	}

	var savederror error
	for i := 0; i <= c.config.Retry; i++ {
		var req *http.Request
		var requestError error
		if rs, ok := body.(io.ReadSeeker); ok {
			if _, err := rs.Seek(0, 0); err != nil {
				return nil, nil, 0, sdk.WrapError(savederror, "request failed after %d retries: %v", i, err)
			}
			req, requestError = http.NewRequest(method, url, body)
		} else {
			req, requestError = http.NewRequest(method, url, bytes.NewBuffer(bodyBytes))
		}
		if requestError != nil {
			savederror = sdk.WithStack(requestError)
			continue
		}

		req = req.WithContext(ctx)
		date := sdk.FormatDateRFC5322(time.Now())
		req.Header.Set("Date", date)
		req.Header.Set("X-CDS-RemoteTime", date)

		if c.config.Verbose {
			log.Printf("Stream > context> %s\n", telemetry.DumpContext(ctx))
		}
		spanCtx, ok := telemetry.ContextToSpanContext(ctx)
		if ok {
			telemetry.DefaultFormat.SpanContextToRequest(spanCtx, req)
		}

		for i := range mods {
			if mods[i] != nil {
				mods[i](req)
			}
		}

		if req.Header.Get("Content-Type") == "" {
			req.Header.Set("Content-Type", "application/json")
		}

		req.Header.Set("Connection", "close")

		//No auth on signing routes or on url that is not cds configured in config.Host
		if strings.HasPrefix(url, c.config.Host) && !signinRouteRegexp.MatchString(path) {
			if _, _, err := new(jwt.Parser).ParseUnverified(c.config.SessionToken, &sdk.AuthSessionJWTClaims{}); err == nil {
				if c.config.Verbose {
					log.Println("JWT recognized")
				}
				auth := "Bearer " + c.config.SessionToken
				req.Header.Add("Authorization", auth)
			}
		}

		if c.config.Verbose {
			log.Println(cli.Green("********REQUEST**********"))
			dmp, _ := httputil.DumpRequestOut(req, true)
			log.Printf("%s", string(dmp))
			log.Println(cli.Green("**************************"))
		}

		var errDo error
		var resp *http.Response
		if noTimeout {
			resp, errDo = c.httpSSEClient.Do(req)
		} else {
			resp, errDo = c.httpClient.Do(req)
		}
		if errDo != nil {
			savederror = sdk.WithStack(errDo)
			continue
		}

		if c.config.Verbose {
			log.Println(cli.Yellow("********RESPONSE**********"))
			dmp, _ := httputil.DumpResponse(resp, true)
			log.Printf("%s", string(dmp))
			log.Println(cli.Yellow("**************************"))
		}

		if resp.StatusCode == 401 {
			c.config.SessionToken = ""
		}

		if resp.StatusCode == 409 || resp.StatusCode > 500 {
			time.Sleep(250 * time.Millisecond)
			savederror = extractBodyErrorFromResponse(resp)
			continue
		}

		// if no request error by status > 500, check CDS error
		// if there is a CDS errors, return it
		if resp.StatusCode == 500 {
			return nil, resp.Header, resp.StatusCode, extractBodyErrorFromResponse(resp)
		}

		return resp.Body, resp.Header, resp.StatusCode, nil
	}

	return nil, nil, 0, sdk.WrapError(savederror, "request failed after %d retries", c.config.Retry)
}

// UploadMultiPart upload multipart
func (c *client) UploadMultiPart(method string, path string, body *bytes.Buffer, mods ...RequestModifier) ([]byte, int, error) {
	// Checks that current session_token is still valid
	// If not, challenge a new one against the authenticationToken
	if !c.config.HasValidSessionToken() && c.config.BuitinConsumerAuthenticationToken != "" {
		resp, err := c.AuthConsumerSignin(sdk.ConsumerBuiltin, sdk.AuthConsumerSigninRequest{"token": c.config.BuitinConsumerAuthenticationToken})
		if err != nil {
			return nil, -1, err
		}
		c.config.SessionToken = resp.Token
	}

	var req *http.Request
	req, errRequest := http.NewRequest(method, c.config.Host+path, body)
	if errRequest != nil {
		return nil, 0, errRequest
	}

	req.Header.Set("Content-Type", "application/octet-stream")
	req.Header.Set("Connection", "close")

	for i := range mods {
		mods[i](req)
	}

	//No auth on signing routes
	if !signinRouteRegexp.MatchString(path) {
		if _, _, err := new(jwt.Parser).ParseUnverified(c.config.SessionToken, &sdk.AuthSessionJWTClaims{}); err == nil {
			if c.config.Verbose {
				fmt.Println("JWT recognized")
			}
			auth := "Bearer " + c.config.SessionToken
			req.Header.Add("Authorization", auth)
		}
	}

	resp, err := c.httpSSEClient.Do(req)
	if err != nil {
		return nil, 0, err
	}
	defer resp.Body.Close()

	if c.config.Verbose {
		fmt.Printf("Response Status: %s\n", resp.Status)
		fmt.Printf("Request path: %s\n", c.config.Host+path)
		fmt.Printf("Request Headers: %s\n", req.Header)
		fmt.Printf("Response Headers: %s\n", resp.Header)
	}

	if resp.StatusCode == 401 {
		c.config.SessionToken = ""
	}

	var respBody []byte
	respBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, resp.StatusCode, err
	}

	if c.config.Verbose {
		if len(body.Bytes()) > 0 {
			fmt.Printf("Response Body: %s\n", body.String())
		}
	}

	return respBody, resp.StatusCode, nil
}
<|endoftext|>


<commit_before>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "0.7.2"
<commit_msg>Tag release v0.7.3<commit_after>// Package aws provides core functionality for making requests to AWS services.
package aws

// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"

// SDKVersion is the version of this SDK
const SDKVersion = "0.7.3"
<|endoftext|>


<commit_before>package azure

import (
	"encoding/json"
	"net/http"
)

// String returns a pointer to the input string. This is useful when initializing
// structures.
func String(input string) *string {
	return &input
}

// isSuccessCode returns true for 200-range numbers which usually denote
// that an HTTP request was successful
func isSuccessCode(statusCode int) bool {
	if statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices {
		return true
	}

	return false
}

// unmarshalFlattenPropertiesAndClose returns a map[string]interface{} with the
// "properties" key flattened for use with mapstructure. It closes the Body reader of
// the http.Response passed in.
func unmarshalFlattenPropertiesAndClose(response *http.Response) (map[string]interface{}, error) {
	return unmarshalNested(response, "properties")
}

// unmarshalFlattenErrorAndClose returns a map[string]interface{} with the
// "error" key flattened for use with mapstructure. It closes the Body reader of
// the http.Response passed in.
func unmarshalFlattenErrorAndClose(response *http.Response) (map[string]interface{}, error) {
	return unmarshalNested(response, "error")
}

func unmarshalNested(response *http.Response, key string) (map[string]interface{}, error) {
	defer response.Body.Close()

	var unmarshalled map[string]interface{}
	decoder := json.NewDecoder(response.Body)

	err := decoder.Decode(&unmarshalled)
	if err != nil {
		return nil, err
	}

	if properties, hasProperties := unmarshalled[key]; hasProperties {
		if propertiesMap, ok := properties.(map[string]interface{}); ok {
			for k, v := range propertiesMap {
				unmarshalled[k] = v
			}

			delete(propertiesMap, key)
		}
	}

	return unmarshalled, nil

}
<commit_msg>Add wrappers for obtaining primitive pointers<commit_after>package azure

import (
	"encoding/json"
	"net/http"
)

// String returns a pointer to the input string. This is useful when initializing
// structures.
func String(input string) *string {
	return &input
}

// Int32 returns a pointer to the input int32. This is useful when initializing
// structures.
func Int32(input int32) *int32 {
	return &input
}

// Int64 returns a pointer to the input int64. This is useful when initializing
// structures.
func Int64(input int64) *int64 {
	return &input
}

// Bool returns a pointer to the input bool. This is useful when initializing
// structures.
func Bool(input bool) *bool {
	return &input
}

// isSuccessCode returns true for 200-range numbers which usually denote
// that an HTTP request was successful
func isSuccessCode(statusCode int) bool {
	if statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices {
		return true
	}

	return false
}

// unmarshalFlattenPropertiesAndClose returns a map[string]interface{} with the
// "properties" key flattened for use with mapstructure. It closes the Body reader of
// the http.Response passed in.
func unmarshalFlattenPropertiesAndClose(response *http.Response) (map[string]interface{}, error) {
	return unmarshalNested(response, "properties")
}

// unmarshalFlattenErrorAndClose returns a map[string]interface{} with the
// "error" key flattened for use with mapstructure. It closes the Body reader of
// the http.Response passed in.
func unmarshalFlattenErrorAndClose(response *http.Response) (map[string]interface{}, error) {
	return unmarshalNested(response, "error")
}

func unmarshalNested(response *http.Response, key string) (map[string]interface{}, error) {
	defer response.Body.Close()

	var unmarshalled map[string]interface{}
	decoder := json.NewDecoder(response.Body)

	err := decoder.Decode(&unmarshalled)
	if err != nil {
		return nil, err
	}

	if properties, hasProperties := unmarshalled[key]; hasProperties {
		if propertiesMap, ok := properties.(map[string]interface{}); ok {
			for k, v := range propertiesMap {
				unmarshalled[k] = v
			}

			delete(propertiesMap, key)
		}
	}

	return unmarshalled, nil

}
<|endoftext|>


<commit_before>package main

type basicEditor struct {
	pos int
	buf []rune
}

// move moves the position.
// Given invalid position, move sets the position at the end of the buffer.
func (e *basicEditor) move(to int) {
	switch {
	case to >= len(e.buf):
		e.pos = len(e.buf)
	case to <= 0:
		e.pos = 0
	default:
		e.pos = to
	}
}
<commit_msg>Update the comment of e.move<commit_after>package main

type basicEditor struct {
	pos int
	buf []rune
}

// move moves the position.
// Given a invalid position, move sets the position at the end of the buffer.
// Valid positions are in range [0, len(e.buf)].
func (e *basicEditor) move(to int) {
	switch {
	case to >= len(e.buf):
		e.pos = len(e.buf)
	case to <= 0:
		e.pos = 0
	default:
		e.pos = to
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/oxtoacart/framed"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"runtime"
	"runtime/pprof"
	"sync"
)

const (
	serverAddr = "127.0.0.1:10081"
)

var (
	mode = flag.String("mode", "server", "Mode (server or client)")
	wg   sync.WaitGroup
)

func main() {
	runtime.GOMAXPROCS(1)
	flag.Parse()
	file, err := os.Create(fmt.Sprintf("/tmp/framed_profile_%s", *mode))
	if err != nil {
		log.Fatal("Unable to create CPU profile file: %s", err)
	}
	pprof.StartCPUProfile(file)
	defer pprof.StopCPUProfile()
	if *mode == "client" {
		client()
	} else {
		server()
	}
}

func server() {
	log.Printf("Starting server at %s", serverAddr)
	if listener, err := net.Listen("tcp", serverAddr); err != nil {
		log.Fatalf("Unable to listen: %s", err)
	} else {
		for {
			if conn, err := listener.Accept(); err != nil {
				log.Printf("Unable to accept: %s", err)
			} else {
				f := framed.NewFramed(conn)
				go func() {
					if frame, err := f.ReadInitial(); err != nil {
						log.Printf("Unable to read initial frame: %s", frame)
					} else {
						for {
							if err := frame.CopyTo(conn); err != nil {
								pprof.StopCPUProfile()
								log.Fatalf("Unable to copy: %s", err)
							} else {
								if frame, err = frame.Next(); err != nil {
									log.Fatalf("Unable to read next frame")
								}
							}
						}
					}
				}()
			}
		}
	}
}

func client() {
	for i := 0; i < 1; i++ {
		wg.Add(1)
		go doClient()
	}
	wg.Wait()
}

func doClient() {
	log.Printf("Starting client connection to server at %s", serverAddr)
	header := []byte{}
	data := []byte("Hell World")
	if conn, err := net.Dial("tcp", serverAddr); err != nil {
		log.Fatalf("Unable to dial server: %s", err)
	} else {
		f := framed.NewFramed(conn)
		// Read
		go io.Copy(ioutil.Discard, conn)

		// Write
		for {
			if err := f.WriteFrame(header, data); err != nil {
				log.Fatalf("Unable to write frame: %s", err)
			}
		}
	}
	wg.Done()
}
<commit_msg>Added ability to run without framing<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/oxtoacart/framed"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"runtime"
	"runtime/pprof"
	"sync"
)

const (
	serverAddr = "127.0.0.1:10081"
)

var (
	mode        = flag.String("mode", "server", "Mode (server or client)")
	shouldFrame = flag.Bool("framed", false, "Whether or not run in framed mode")
	wg          sync.WaitGroup
)

func main() {
	runtime.GOMAXPROCS(1)
	flag.Parse()
	file, err := os.Create(fmt.Sprintf("/tmp/framed_profile_%s", *mode))
	if err != nil {
		log.Fatal("Unable to create CPU profile file: %s", err)
	}
	pprof.StartCPUProfile(file)
	defer pprof.StopCPUProfile()
	if *mode == "client" {
		client()
	} else {
		server()
	}
}

func server() {
	log.Printf("Starting server at %s", serverAddr)
	if listener, err := net.Listen("tcp", serverAddr); err != nil {
		log.Fatalf("Unable to listen: %s", err)
	} else {
		for {
			if conn, err := listener.Accept(); err != nil {
				log.Printf("Unable to accept: %s", err)
			} else {
				if *shouldFrame {
					f := framed.NewFramed(conn)
					go func() {
						if frame, err := f.ReadInitial(); err != nil {
							log.Printf("Unable to read initial frame: %s", frame)
						} else {
							for {
								if err := frame.CopyTo(conn); err != nil {
									pprof.StopCPUProfile()
									log.Fatalf("Unable to copy: %s", err)
								} else {
									if frame, err = frame.Next(); err != nil {
										log.Fatalf("Unable to read next frame")
									}
								}
							}
						}
					}()
				} else {
					go io.Copy(conn, conn)
				}
			}
		}
	}
}

func client() {
	for i := 0; i < 1; i++ {
		wg.Add(1)
		go doClient()
	}
	wg.Wait()
}

func doClient() {
	log.Printf("Starting client connection to server at %s", serverAddr)
	header := []byte{}
	data := []byte("Hell World")
	if conn, err := net.Dial("tcp", serverAddr); err != nil {
		log.Fatalf("Unable to dial server: %s", err)
	} else {
		f := framed.NewFramed(conn)
		// Read
		go io.Copy(ioutil.Discard, conn)

		// Write
		for {
			if err := f.WriteFrame(header, data); err != nil {
				log.Fatalf("Unable to write frame: %s", err)
			}
		}
	}
	wg.Done()
}
<|endoftext|>