<commit_before>package cache

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/fatih/goset"
	"net/http"
	"net/http/httputil"
	"strings"
	"sync"
)

// Cache interface is used by CacheTransport to store and retrieve the cache content.
type Cache interface {
	Get(key string) ([]byte, bool)
	Set(key string, value []byte)
	Delete(key string)
}

type CacheTransport struct {
	cache     Cache
	transport http.RoundTripper
	suffixes  *goset.Set
}

func NewCacheTransport(suffixes string) http.RoundTripper {
	set := goset.New()

	for _, suffix := range strings.Split(suffixes, ",") {
		set.Add(strings.TrimSpace(suffix))
	}

	return &CacheTransport{
		cache:     NewMemoryCache(),
		transport: http.DefaultTransport,
		suffixes:  set,
	}
}

func (c *CacheTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if !c.cacheableRequest(req) {
		return c.transport.RoundTrip(req)
	}

	cache, err := c.load(req)
	fmt.Println("err", err)
	if err == nil {
		fmt.Printf("==== GET CACHE ====: '%s'\n", c.cacheKey(req))
		return cache, nil
	}

	resp, err := c.transport.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	if c.cacheableResponse(resp) {
		c.save(req, resp)
	}

	return resp, nil
}

func (c *CacheTransport) cacheKey(req *http.Request) string {
	return req.URL.String()
}

// cacheableRequest defines if a request is cachable. Only requests with GET
// methods and files that matches the cachePrefix rule is cached.
func (c *CacheTransport) cacheableRequest(req *http.Request) bool {
	if req.Method != "GET" {
		return false
	}

	return true
}

// cacheableResponse defines whether a response is cacheable or not.We only
// cache responses with 200. 301 and 302 are not cached currently.
func (c *CacheTransport) cacheableResponse(resp *http.Response) bool {
	return resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotModified
}

// load prepares the response of a request by loading its body from cache.
func (c *CacheTransport) load(req *http.Request) (*http.Response, error) {
	key := c.cacheKey(req)
	fmt.Println("loading from cache key", key)
	cachedResp, ok := c.cache.Get(key)
	if !ok {
		return nil, fmt.Errorf("cache is not available for: %s", key)
	}

	b := bytes.NewBuffer(cachedResp)
	return http.ReadResponse(bufio.NewReader(b), req)
}

// save saves the body of a response corresponding to a request.
func (c *CacheTransport) save(req *http.Request, resp *http.Response) error {
	key := c.cacheKey(req)
	respBytes, err := httputil.DumpResponse(resp, true)
	if err != nil {
		return err
	}

	fmt.Printf("==== SAVE CACHE ====: '%s'\n", key)
	c.cache.Set(key, respBytes)
	return nil
}

type MemoryCache struct {
	sync.RWMutex
	items map[string][]byte
}

func NewMemoryCache() *MemoryCache {
	return &MemoryCache{
		items: map[string][]byte{},
	}
}

func (c *MemoryCache) Get(key string) ([]byte, bool) {
	c.RLock()
	defer c.RUnlock()

	value, ok := c.items[key]
	return value, ok
}

func (c *MemoryCache) Set(key string, resp []byte) {
	c.Lock()
	defer c.Unlock()

	c.items[key] = resp
}

func (c *MemoryCache) Delete(key string) {
	c.Lock()
	defer c.Unlock()

	delete(c.items, key)
}
<commit_msg>kontrolproxy/cache: implement suffix based validation, works like a charm<commit_after>package cache

import (
	"bufio"
	"bytes"
	"fmt"
	"github.com/fatih/goset"
	"net/http"
	"net/http/httputil"
	"path/filepath"
	"strings"
	"sync"
)

// Cache interface is used by CacheTransport to store and retrieve the cache content.
type Cache interface {
	Get(key string) ([]byte, bool)
	Set(key string, value []byte)
	Delete(key string)
}

type CacheTransport struct {
	cache     Cache
	transport http.RoundTripper
	suffixes  *goset.Set
}

func NewCacheTransport(suffixes string) http.RoundTripper {
	set := goset.New()

	for _, suffix := range strings.Split(suffixes, ",") {
		set.Add(strings.TrimSpace(suffix))
	}

	return &CacheTransport{
		cache:     NewMemoryCache(),
		transport: http.DefaultTransport,
		suffixes:  set,
	}
}

func (c *CacheTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	if !c.cacheableRequest(req) {
		return c.transport.RoundTrip(req)
	}

	cache, err := c.load(req)
	if err == nil {
		return cache, nil
	}

	resp, err := c.transport.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	if c.cacheableResponse(resp) {
		c.save(req, resp)
	}

	return resp, nil
}

func (c *CacheTransport) cacheKey(req *http.Request) string {
	return req.URL.String()
}

// cacheableRequest defines if a request is cachable. Only requests with GET
// methods and files that matches the cachePrefix rule is cached.
func (c *CacheTransport) cacheableRequest(req *http.Request) bool {
	if req.Method != "GET" {
		return false
	}

	suffix := filepath.Ext(req.URL.String())
	if suffix == "" {
		return false
	}

	// filepath.Ext returns .js for sample.js, therefore remove the dot
	if !c.suffixes.Has(suffix[1:]) {
		return false
	}

	return true
}

// cacheableResponse defines whether a response is cacheable or not.We only
// cache responses with 200. 301 and 302 are not cached currently.
func (c *CacheTransport) cacheableResponse(resp *http.Response) bool {
	return resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNotModified
}

// load prepares the response of a request by loading its body from cache.
func (c *CacheTransport) load(req *http.Request) (*http.Response, error) {
	key := c.cacheKey(req)
	cachedResp, ok := c.cache.Get(key)
	if !ok {
		return nil, fmt.Errorf("cache is not available for: %s", key)
	}

	b := bytes.NewBuffer(cachedResp)
	return http.ReadResponse(bufio.NewReader(b), req)
}

// save saves the body of a response corresponding to a request.
func (c *CacheTransport) save(req *http.Request, resp *http.Response) error {
	key := c.cacheKey(req)
	respBytes, err := httputil.DumpResponse(resp, true)
	if err != nil {
		return err
	}

	c.cache.Set(key, respBytes)
	return nil
}

type MemoryCache struct {
	sync.RWMutex
	items map[string][]byte
}

func NewMemoryCache() *MemoryCache {
	return &MemoryCache{
		items: map[string][]byte{},
	}
}

func (c *MemoryCache) Get(key string) ([]byte, bool) {
	c.RLock()
	defer c.RUnlock()

	value, ok := c.items[key]
	return value, ok
}

func (c *MemoryCache) Set(key string, resp []byte) {
	c.Lock()
	defer c.Unlock()

	c.items[key] = resp
}

func (c *MemoryCache) Delete(key string) {
	c.Lock()
	defer c.Unlock()

	delete(c.items, key)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess Authors.

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

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

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

package preparestmt

import (
	"database/sql"
	"fmt"
	"testing"
	"time"

	"github.com/icrowley/fake"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestSelect simple select the data without any condition.
func TestSelect(t *testing.T) {
	dbo := Connect(t)
	defer dbo.Close()
	selectWhere(t, dbo, "")
}

// TestInsertUpdateDelete validates all insert, update and
// delete method on prepared statements.
func TestInsertUpdateDelete(t *testing.T) {

	dbo := Connect(t)
	defer dbo.Close()
	// prepare insert statement
	insertStmt := `insert into ` + tableName + ` values(?,  ?,  ?,  ?,  ?,  ?,  ?,
		?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?, ?,  ?,  ?,  ?,  ?,  ?, ?, ?, ?);`

	textValue := fake.FullName()
	largeComment := fake.Paragraph()

	// inserting multiple rows into test table
	for i := 1; i <= 100; i++ {
		// preparing value for the insert testing
		insertValue := []interface{}{
			i, fmt.Sprint(i) + "21", i * 100,
			127, 1, 32767, 8388607, 2147483647, 2.55, 64.9, 55.5,
			time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
			time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
			time.Now(),
			time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
			1, 1, 1, 1, 1, 1, 1, 1, 1, jsonExample, textValue, largeComment,
		}
		exec(t, dbo, insertStmt, insertValue...)

	}
	// validate inserted data count
	testcount(t, dbo, 100)

	// select data with id 1 and validate the data accordingly
	// validate row count
	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 1, len(data))

	// validate value of msg column in data
	assert.Equal(t, fmt.Sprintf("%d21", testingID), data[0].Msg)

	// testing record update
	updateRecord(t, dbo)

	// testing record deletion
	deleteRecord(t, dbo)

	// testing recontion and deleted data validation
	reconnectAndTest(t)
}

// testcount validates inserted rows count with expected count.
func testcount(t *testing.T, dbo *sql.DB, except int) {
	r, err := dbo.Query("SELECT count(1) FROM " + tableName)
	require.Nil(t, err)

	r.Next()
	var i int
	err = r.Scan(&i)
	assert.Nil(t, err)
	assert.Equal(t, except, i)
}

// TestAutoIncColumns test insertion of row without passing
// the value of auto increment columns (here it is id).
func TestAutoIncColumns(t *testing.T) {
	dbo := Connect(t)
	defer dbo.Close()
	// insert a row without id
	insertStmt := "INSERT INTO " + tableName + ` (
		msg,keyspace_id,tinyint_unsigned,bool_signed,smallint_unsigned,
		mediumint_unsigned,int_unsigned,float_unsigned,double_unsigned,
		decimal_unsigned,t_date,t_datetime,t_time,t_timestamp,c8,c16,c24,
		c32,c40,c48,c56,c63,c64,json_col,text_col,data) VALUES (?,  ?,  ?,  ?,  ?, ?,
		  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?, ?,  ?,  ?,  ?,  ?,  ?, ?, ?, ?);`
	insertValue := []interface{}{
		"21", 0,
		127, 1, 32767, 8388607, 2147483647, 2.55, 64.9, 55.5,
		time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
		time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
		time.Now(),
		time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
		1, 1, 1, 1, 1, 1, 1, 1, 1, jsonExample, fake.DomainName(), fake.Paragraph(),
	}

	exec(t, dbo, insertStmt, insertValue...)
}

// deleteRecord test deletion operation corresponds to the testingID.
func deleteRecord(t *testing.T, dbo *sql.DB) {
	// delete the record with id 1
	exec(t, dbo, "DELETE FROM "+tableName+" WHERE id = ?;", testingID)

	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 0, len(data))

}

// updateRecord test update operation corresponds to the testingID.
func updateRecord(t *testing.T, dbo *sql.DB) {
	// update the record with id 1
	updateData := "new data value"
	updateTextCol := "new text col value"
	updateQuery := "update " + tableName + " set data = ? , text_col = ? where id = ?;"

	exec(t, dbo, updateQuery, updateData, updateTextCol, testingID)

	// validate the updated value
	// validate row count
	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 1, len(data))

	// validate value of msg column in data
	assert.Equal(t, updateData, data[0].Data)
	assert.Equal(t, updateTextCol, data[0].TextCol)

}

// reconnectAndTest creates new connection with database and validate.
func reconnectAndTest(t *testing.T) {
	// reconnect and try to select the record with id 1
	dbo := Connect(t)
	defer dbo.Close()
	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 0, len(data))

}

// TestWrongTableName query database using invalid
// tablename and validate error.
func TestWrongTableName(t *testing.T) {
	dbo := Connect(t)
	defer dbo.Close()
	execWithError(t, dbo, []uint16{1105}, "select * from teseting_table;")
}
<commit_msg>prepare_statement: dummy commit.<commit_after>/*
Copyright 2019 The Vitess Authors.

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

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

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

package preparestmt

import (
	"database/sql"
	"fmt"
	"testing"
	"time"

	"github.com/icrowley/fake"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestSelect simple select the data without any condition.
func TestSelect(t *testing.T) {
	dbo := Connect(t)
	defer dbo.Close()
	selectWhere(t, dbo, "")
}

// TestInsertUpdateDelete validates all insert, update and
// delete method on prepared statements.
func TestInsertUpdateDelete(t *testing.T) {

	dbo := Connect(t)
	defer dbo.Close()
	// prepare insert statement
	insertStmt := `insert into ` + tableName + ` values( ?,  ?,  ?,  ?,  ?,  ?,  ?,
		?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?, ?,  ?,  ?,  ?,  ?,  ?, ?, ?, ?);`

	textValue := fake.FullName()
	largeComment := fake.Paragraph()

	// inserting multiple rows into test table
	for i := 1; i <= 100; i++ {
		// preparing value for the insert testing
		insertValue := []interface{}{
			i, fmt.Sprint(i) + "21", i * 100,
			127, 1, 32767, 8388607, 2147483647, 2.55, 64.9, 55.5,
			time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
			time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
			time.Now(),
			time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
			1, 1, 1, 1, 1, 1, 1, 1, 1, jsonExample, textValue, largeComment,
		}
		exec(t, dbo, insertStmt, insertValue...)

	}
	// validate inserted data count
	testcount(t, dbo, 100)

	// select data with id 1 and validate the data accordingly
	// validate row count
	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 1, len(data))

	// validate value of msg column in data
	assert.Equal(t, fmt.Sprintf("%d21", testingID), data[0].Msg)

	// testing record update
	updateRecord(t, dbo)

	// testing record deletion
	deleteRecord(t, dbo)

	// testing recontion and deleted data validation
	reconnectAndTest(t)
}

// testcount validates inserted rows count with expected count.
func testcount(t *testing.T, dbo *sql.DB, except int) {
	r, err := dbo.Query("SELECT count(1) FROM " + tableName)
	require.Nil(t, err)

	r.Next()
	var i int
	err = r.Scan(&i)
	assert.Nil(t, err)
	assert.Equal(t, except, i)
}

// TestAutoIncColumns test insertion of row without passing
// the value of auto increment columns (here it is id).
func TestAutoIncColumns(t *testing.T) {
	dbo := Connect(t)
	defer dbo.Close()
	// insert a row without id
	insertStmt := "INSERT INTO " + tableName + ` (
		msg,keyspace_id,tinyint_unsigned,bool_signed,smallint_unsigned,
		mediumint_unsigned,int_unsigned,float_unsigned,double_unsigned,
		decimal_unsigned,t_date,t_datetime,t_time,t_timestamp,c8,c16,c24,
		c32,c40,c48,c56,c63,c64,json_col,text_col,data) VALUES (?,  ?,  ?,  ?,  ?, ?,
		  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?,  ?, ?,  ?,  ?,  ?,  ?,  ?, ?, ?, ?);`
	insertValue := []interface{}{
		"21", 0,
		127, 1, 32767, 8388607, 2147483647, 2.55, 64.9, 55.5,
		time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
		time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
		time.Now(),
		time.Date(2009, 5, 5, 0, 0, 0, 0, time.UTC),
		1, 1, 1, 1, 1, 1, 1, 1, 1, jsonExample, fake.DomainName(), fake.Paragraph(),
	}

	exec(t, dbo, insertStmt, insertValue...)
}

// deleteRecord test deletion operation corresponds to the testingID.
func deleteRecord(t *testing.T, dbo *sql.DB) {
	// delete the record with id 1
	exec(t, dbo, "DELETE FROM "+tableName+" WHERE id = ?;", testingID)

	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 0, len(data))

}

// updateRecord test update operation corresponds to the testingID.
func updateRecord(t *testing.T, dbo *sql.DB) {
	// update the record with id 1
	updateData := "new data value"
	updateTextCol := "new text col value"
	updateQuery := "update " + tableName + " set data = ? , text_col = ? where id = ?;"

	exec(t, dbo, updateQuery, updateData, updateTextCol, testingID)

	// validate the updated value
	// validate row count
	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 1, len(data))

	// validate value of msg column in data
	assert.Equal(t, updateData, data[0].Data)
	assert.Equal(t, updateTextCol, data[0].TextCol)

}

// reconnectAndTest creates new connection with database and validate.
func reconnectAndTest(t *testing.T) {
	// reconnect and try to select the record with id 1
	dbo := Connect(t)
	defer dbo.Close()
	data := selectWhere(t, dbo, "id = ?", testingID)
	assert.Equal(t, 0, len(data))

}

// TestWrongTableName query database using invalid
// tablename and validate error.
func TestWrongTableName(t *testing.T) {
	dbo := Connect(t)
	defer dbo.Close()
	execWithError(t, dbo, []uint16{1105}, "select * from teseting_table;")
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"errors"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
)

func NewCmdLogin(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	cmd := cli.Command{
		Name:         "login",
		ArgumentHelp: "[username]",
		Usage:        "Establish a session with the keybase server",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(NewCmdLoginRunner(g), "login", c)
		},
	}
	// Note we'll only be able to set this via mode via Environment variable
	// since it's too early to check command-line setting of it.
	if g.Env.GetRunMode() == libkb.DevelRunMode {
		cmd.Flags = append(cmd.Flags, cli.BoolFlag{
			Name:  "emulate-gui",
			Usage: "emulate GUI signing and fork GPG from the service",
		})
	}
	return cmd
}

type CmdLogin struct {
	libkb.Contextified
	username   string
	clientType keybase1.ClientType
	cancel     func()
}

func NewCmdLoginRunner(g *libkb.GlobalContext) *CmdLogin {
	return &CmdLogin{
		Contextified: libkb.NewContextified(g),
		clientType:   keybase1.ClientType_CLI,
	}
}

func (c *CmdLogin) Run() error {
	protocols := []rpc.Protocol{
		NewProvisionUIProtocol(c.G(), libkb.KexRoleProvisionee),
		NewLoginUIProtocol(c.G()),
		NewSecretUIProtocol(c.G()),
		NewGPGUIProtocol(c.G()),
	}
	if err := RegisterProtocolsWithContext(protocols, c.G()); err != nil {
		return err
	}
	client, err := GetLoginClient(c.G())
	if err != nil {
		return err
	}

	// TODO: it would be nice to move this up a level and have keybase/main.go create
	// a context and pass it to Command.Run(), then it can handle cancel itself
	// instead of using Command.Cancel().
	ctx, cancel := context.WithCancel(context.Background())
	c.cancel = cancel
	defer c.cancel()

	return client.Login(ctx,
		keybase1.LoginArg{
			Username:   c.username,
			DeviceType: libkb.DeviceTypeDesktop,
			ClientType: c.clientType,
		})
}

func (c *CmdLogin) ParseArgv(ctx *cli.Context) error {
	nargs := len(ctx.Args())
	if nargs > 1 {
		return errors.New("Invalid arguments.")
	}

	if nargs == 1 {
		c.username = ctx.Args()[0]
		if libkb.CheckEmail.F(c.username) {
			return errors.New("Please login again via `keybase login [username]`")
		}
		if !libkb.CheckUsername.F(c.username) {
			return errors.New("Invalid username format. Please login again via `keybase login [username]`")
		}
	}
	if ctx.Bool("emulate-gui") {
		c.clientType = keybase1.ClientType_GUI
	}
	return nil
}

func (c *CmdLogin) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config:    true,
		KbKeyring: true,
		API:       true,
	}
}

func (c *CmdLogin) Cancel() error {
	c.G().Log.Debug("received request to cancel running login command")
	if c.cancel != nil {
		c.G().Log.Debug("command cancel function exists, calling it")
		c.cancel()

		// hack:
		// In go-framed-msgpack-rpc, dispatch.handleCall() starts a goroutine to check the context being
		// canceled.  Without this sleep, there's (often) no time for the goroutine to receive the
		// context.Done() before keybase/main.go shuts down everything.
		// In practice, the sleep never lasts 5 seconds, just long enough for everything to get canceled.
		time.Sleep(5 * time.Second)
	}
	return nil
}
<commit_msg>Wait on done chan<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package client

import (
	"errors"

	"golang.org/x/net/context"

	"github.com/keybase/cli"
	"github.com/keybase/client/go/libcmdline"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
)

func NewCmdLogin(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command {
	cmd := cli.Command{
		Name:         "login",
		ArgumentHelp: "[username]",
		Usage:        "Establish a session with the keybase server",
		Action: func(c *cli.Context) {
			cl.ChooseCommand(NewCmdLoginRunner(g), "login", c)
		},
	}
	// Note we'll only be able to set this via mode via Environment variable
	// since it's too early to check command-line setting of it.
	if g.Env.GetRunMode() == libkb.DevelRunMode {
		cmd.Flags = append(cmd.Flags, cli.BoolFlag{
			Name:  "emulate-gui",
			Usage: "emulate GUI signing and fork GPG from the service",
		})
	}
	return cmd
}

type CmdLogin struct {
	libkb.Contextified
	username   string
	clientType keybase1.ClientType
	cancel     func()
	done       chan bool
}

func NewCmdLoginRunner(g *libkb.GlobalContext) *CmdLogin {
	return &CmdLogin{
		Contextified: libkb.NewContextified(g),
		clientType:   keybase1.ClientType_CLI,
		done:         make(chan bool, 1),
	}
}

func (c *CmdLogin) Run() error {
	protocols := []rpc.Protocol{
		NewProvisionUIProtocol(c.G(), libkb.KexRoleProvisionee),
		NewLoginUIProtocol(c.G()),
		NewSecretUIProtocol(c.G()),
		NewGPGUIProtocol(c.G()),
	}
	if err := RegisterProtocolsWithContext(protocols, c.G()); err != nil {
		return err
	}
	client, err := GetLoginClient(c.G())
	if err != nil {
		return err
	}

	// TODO: it would be nice to move this up a level and have keybase/main.go create
	// a context and pass it to Command.Run(), then it can handle cancel itself
	// instead of using Command.Cancel().
	ctx, cancel := context.WithCancel(context.Background())
	c.cancel = cancel
	defer func() {
		c.cancel()
		c.cancel = nil
	}()

	err = client.Login(ctx,
		keybase1.LoginArg{
			Username:   c.username,
			DeviceType: libkb.DeviceTypeDesktop,
			ClientType: c.clientType,
		})
	c.done <- true
	return err
}

func (c *CmdLogin) ParseArgv(ctx *cli.Context) error {
	nargs := len(ctx.Args())
	if nargs > 1 {
		return errors.New("Invalid arguments.")
	}

	if nargs == 1 {
		c.username = ctx.Args()[0]
		if libkb.CheckEmail.F(c.username) {
			return errors.New("Please login again via `keybase login [username]`")
		}
		if !libkb.CheckUsername.F(c.username) {
			return errors.New("Invalid username format. Please login again via `keybase login [username]`")
		}
	}
	if ctx.Bool("emulate-gui") {
		c.clientType = keybase1.ClientType_GUI
	}
	return nil
}

func (c *CmdLogin) GetUsage() libkb.Usage {
	return libkb.Usage{
		Config:    true,
		KbKeyring: true,
		API:       true,
	}
}

func (c *CmdLogin) Cancel() error {
	c.G().Log.Debug("received request to cancel running login command")
	if c.cancel != nil {
		c.G().Log.Debug("command cancel function exists, calling it")
		c.cancel()

		// In go-framed-msgpack-rpc, dispatch.handleCall() starts a goroutine to check the context being
		// canceled.
		// So, need to wait here for call to actually finish in order for the cancel message to make it
		// to the daemon.
		c.G().Log.Debug("waiting for command to finish")
		<-c.done
		c.G().Log.Debug("command finished, cancel complete")
	}
	return nil
}
<|endoftext|>


<commit_before>// Package client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"

	ct "github.com/google/certificate-transparency/go"
	"golang.org/x/net/context"
)

// URI paths for CT Log endpoints
const (
	AddChainPath    = "/ct/v1/add-chain"
	AddPreChainPath = "/ct/v1/add-pre-chain"
	AddJSONPath     = "/ct/v1/add-json"
	GetSTHPath      = "/ct/v1/get-sth"
	GetEntriesPath  = "/ct/v1/get-entries"
)

// LogClient represents a client for a given CT Log instance
type LogClient struct {
	uri        string       // the base URI of the log. e.g. http://ct.googleapis/pilot
	httpClient *http.Client // used to interact with the log via HTTP
}

//////////////////////////////////////////////////////////////////////////////////
// JSON structures follow.
// These represent the structures returned by the CT Log server.
//////////////////////////////////////////////////////////////////////////////////

// addChainRequest represents the JSON request body sent to the add-chain CT
// method.
type addChainRequest struct {
	Chain []string `json:"chain"`
}

// addChainResponse represents the JSON response to the add-chain CT method.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type addChainResponse struct {
	SCTVersion ct.Version `json:"sct_version"` // SCT structure version
	ID         string     `json:"id"`          // Log ID
	Timestamp  uint64     `json:"timestamp"`   // Timestamp of issuance
	Extensions string     `json:"extensions"`  // Holder for any CT extensions
	Signature  string     `json:"signature"`   // Log signature for this SCT
}

// addJSONRequest represents the JSON request body sent ot the add-json CT
// method.
type addJSONRequest struct {
	Data interface{} `json:"data"`
}

// getSTHResponse respresents the JSON response to the get-sth CT method
type getSTHResponse struct {
	TreeSize          uint64 `json:"tree_size"`           // Number of certs in the current tree
	Timestamp         uint64 `json:"timestamp"`           // Time that the tree was created
	SHA256RootHash    string `json:"sha256_root_hash"`    // Root hash of the tree
	TreeHeadSignature string `json:"tree_head_signature"` // Log signature for this STH
}

// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method
type getConsistencyProofResponse struct {
	Consistency []string `json:"consistency"`
}

// getAuditProofResponse represents the JSON response to the CT get-audit-proof method
type getAuditProofResponse struct {
	Hash     []string `json:"hash"`      // the hashes which make up the proof
	TreeSize uint64   `json:"tree_size"` // the tree size against which this proof is constructed
}

// getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
type getAcceptedRootsResponse struct {
	Certificates []string `json:"certificates"`
}

// getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
type getEntryAndProofResponse struct {
	LeafInput string   `json:"leaf_input"` // the entry itself
	ExtraData string   `json:"extra_data"` // any chain provided when the entry was added to the log
	AuditPath []string `json:"audit_path"` // the corresponding proof
}

// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
// |hc| is the underlying client to be used for HTTP requests to the CT log.
func New(uri string, hc *http.Client) *LogClient {
	if hc == nil {
		hc = new(http.Client)
	}
	return &LogClient{uri: uri, httpClient: hc}
}

// Makes a HTTP call to |uri|, and attempts to parse the response as a
// JSON representation of the structure in |res|. Uses |httpClient| to
// make the actual HTTP call.
// Returns a non-nil |error| if there was a problem.
func fetchAndParse(httpClient *http.Client, uri string, res interface{}) error {
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return err
	}
	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	// Make sure everything is read, so http.Client can reuse the connection.
	defer ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		return fmt.Errorf("got HTTP Status %s", resp.Status)
	}

	if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
		return err
	}

	return nil
}

// Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
	postBody, err := json.Marshal(req)
	if err != nil {
		return nil, "", err
	}
	httpReq, err := http.NewRequest("POST", uri, bytes.NewReader(postBody))
	if err != nil {
		return nil, "", err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	// Read all of the body, if there is one, so that the http.Client can do
	// Keep-Alive:
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
	}
	if err != nil {
		return resp, string(body), err
	}
	if resp.StatusCode == 200 {
		if err != nil {
			return resp, string(body), err
		}
		if err = json.Unmarshal(body, &res); err != nil {
			return resp, string(body), err
		}
	}
	return resp, string(body), nil
}

func backoffForRetry(ctx context.Context, d time.Duration) error {
	backoffTimer := time.NewTimer(d)
	if ctx != nil {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-backoffTimer.C:
		}
	} else {
		<-backoffTimer.C
	}
	return nil
}

// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	var resp addChainResponse
	var req addChainRequest
	for _, link := range chain {
		req.Chain = append(req.Chain, base64.StdEncoding.EncodeToString(link))
	}
	httpStatus := "Unknown"
	backoffSeconds := 0
	done := false
	for !done {
		if backoffSeconds > 0 {
			log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
		}
		err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
		if err != nil {
			return nil, err
		}
		if backoffSeconds > 0 {
			backoffSeconds = 0
		}
		httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp)
		if err != nil {
			backoffSeconds = 10
			continue
		}
		switch {
		case httpResp.StatusCode == 200:
			done = true
		case httpResp.StatusCode == 408:
			// request timeout, retry immediately
		case httpResp.StatusCode == 503:
			// Retry
			backoffSeconds = 10
			if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					backoffSeconds = seconds
				}
			}
		default:
			return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody)
		}
		httpStatus = httpResp.Status
	}

	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddChainPath, chain)
}

// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddPreChainPath, chain)
}

// AddChainWithContext adds the (DER represented) X509 |chain| to the log and
// fails if the provided context expires before the chain is submitted.
func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(ctx, AddChainPath, chain)
}

func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
	req := addJSONRequest{
		Data: data,
	}
	var resp addChainResponse
	_, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
	if err != nil {
		return nil, err
	}
	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error.
func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
	var resp getSTHResponse
	if err = fetchAndParse(c.httpClient, c.uri+GetSTHPath, &resp); err != nil {
		return
	}
	sth = &ct.SignedTreeHead{
		TreeSize:  resp.TreeSize,
		Timestamp: resp.Timestamp,
	}

	rawRootHash, err := base64.StdEncoding.DecodeString(resp.SHA256RootHash)
	if err != nil {
		return nil, fmt.Errorf("invalid base64 encoding in sha256_root_hash: %v", err)
	}
	if len(rawRootHash) != sha256.Size {
		return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(rawRootHash))
	}
	copy(sth.SHA256RootHash[:], rawRootHash)

	rawSignature, err := base64.StdEncoding.DecodeString(resp.TreeHeadSignature)
	if err != nil {
		return nil, errors.New("invalid base64 encoding in tree_head_signature")
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	// TODO(alcutter): Verify signature
	sth.TreeHeadSignature = *ds
	return
}
<commit_msg>Use constants from the http package for the methods.<commit_after>// Package client is a CT log client implementation and contains types and code
// for interacting with RFC6962-compliant CT Log instances.
// See http://tools.ietf.org/html/rfc6962 for details
package client

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strconv"
	"time"

	ct "github.com/google/certificate-transparency/go"
	"golang.org/x/net/context"
)

// URI paths for CT Log endpoints
const (
	AddChainPath    = "/ct/v1/add-chain"
	AddPreChainPath = "/ct/v1/add-pre-chain"
	AddJSONPath     = "/ct/v1/add-json"
	GetSTHPath      = "/ct/v1/get-sth"
	GetEntriesPath  = "/ct/v1/get-entries"
)

// LogClient represents a client for a given CT Log instance
type LogClient struct {
	uri        string       // the base URI of the log. e.g. http://ct.googleapis/pilot
	httpClient *http.Client // used to interact with the log via HTTP
}

//////////////////////////////////////////////////////////////////////////////////
// JSON structures follow.
// These represent the structures returned by the CT Log server.
//////////////////////////////////////////////////////////////////////////////////

// addChainRequest represents the JSON request body sent to the add-chain CT
// method.
type addChainRequest struct {
	Chain []string `json:"chain"`
}

// addChainResponse represents the JSON response to the add-chain CT method.
// An SCT represents a Log's promise to integrate a [pre-]certificate into the
// log within a defined period of time.
type addChainResponse struct {
	SCTVersion ct.Version `json:"sct_version"` // SCT structure version
	ID         string     `json:"id"`          // Log ID
	Timestamp  uint64     `json:"timestamp"`   // Timestamp of issuance
	Extensions string     `json:"extensions"`  // Holder for any CT extensions
	Signature  string     `json:"signature"`   // Log signature for this SCT
}

// addJSONRequest represents the JSON request body sent ot the add-json CT
// method.
type addJSONRequest struct {
	Data interface{} `json:"data"`
}

// getSTHResponse respresents the JSON response to the get-sth CT method
type getSTHResponse struct {
	TreeSize          uint64 `json:"tree_size"`           // Number of certs in the current tree
	Timestamp         uint64 `json:"timestamp"`           // Time that the tree was created
	SHA256RootHash    string `json:"sha256_root_hash"`    // Root hash of the tree
	TreeHeadSignature string `json:"tree_head_signature"` // Log signature for this STH
}

// getConsistencyProofResponse represents the JSON response to the CT get-consistency-proof method
type getConsistencyProofResponse struct {
	Consistency []string `json:"consistency"`
}

// getAuditProofResponse represents the JSON response to the CT get-audit-proof method
type getAuditProofResponse struct {
	Hash     []string `json:"hash"`      // the hashes which make up the proof
	TreeSize uint64   `json:"tree_size"` // the tree size against which this proof is constructed
}

// getAcceptedRootsResponse represents the JSON response to the CT get-roots method.
type getAcceptedRootsResponse struct {
	Certificates []string `json:"certificates"`
}

// getEntryAndProodReponse represents the JSON response to the CT get-entry-and-proof method
type getEntryAndProofResponse struct {
	LeafInput string   `json:"leaf_input"` // the entry itself
	ExtraData string   `json:"extra_data"` // any chain provided when the entry was added to the log
	AuditPath []string `json:"audit_path"` // the corresponding proof
}

// New constructs a new LogClient instance.
// |uri| is the base URI of the CT log instance to interact with, e.g.
// http://ct.googleapis.com/pilot
// |hc| is the underlying client to be used for HTTP requests to the CT log.
func New(uri string, hc *http.Client) *LogClient {
	if hc == nil {
		hc = new(http.Client)
	}
	return &LogClient{uri: uri, httpClient: hc}
}

// Makes a HTTP call to |uri|, and attempts to parse the response as a
// JSON representation of the structure in |res|. Uses |httpClient| to
// make the actual HTTP call.
// Returns a non-nil |error| if there was a problem.
func fetchAndParse(httpClient *http.Client, uri string, res interface{}) error {
	req, err := http.NewRequest(http.MethodGet, uri, nil)
	if err != nil {
		return err
	}
	resp, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	// Make sure everything is read, so http.Client can reuse the connection.
	defer ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		return fmt.Errorf("got HTTP Status %s", resp.Status)
	}

	if err := json.NewDecoder(resp.Body).Decode(res); err != nil {
		return err
	}

	return nil
}

// Makes a HTTP POST call to |uri|, and attempts to parse the response as a JSON
// representation of the structure in |res|.
// Returns a non-nil |error| if there was a problem.
func (c *LogClient) postAndParse(uri string, req interface{}, res interface{}) (*http.Response, string, error) {
	postBody, err := json.Marshal(req)
	if err != nil {
		return nil, "", err
	}
	httpReq, err := http.NewRequest(http.MethodPost, uri, bytes.NewReader(postBody))
	if err != nil {
		return nil, "", err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(httpReq)
	// Read all of the body, if there is one, so that the http.Client can do
	// Keep-Alive:
	var body []byte
	if resp != nil {
		body, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
	}
	if err != nil {
		return resp, string(body), err
	}
	if resp.StatusCode == 200 {
		if err != nil {
			return resp, string(body), err
		}
		if err = json.Unmarshal(body, &res); err != nil {
			return resp, string(body), err
		}
	}
	return resp, string(body), nil
}

func backoffForRetry(ctx context.Context, d time.Duration) error {
	backoffTimer := time.NewTimer(d)
	if ctx != nil {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-backoffTimer.C:
		}
	} else {
		<-backoffTimer.C
	}
	return nil
}

// Attempts to add |chain| to the log, using the api end-point specified by
// |path|. If provided context expires before submission is complete an
// error will be returned.
func (c *LogClient) addChainWithRetry(ctx context.Context, path string, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	var resp addChainResponse
	var req addChainRequest
	for _, link := range chain {
		req.Chain = append(req.Chain, base64.StdEncoding.EncodeToString(link))
	}
	httpStatus := "Unknown"
	backoffSeconds := 0
	done := false
	for !done {
		if backoffSeconds > 0 {
			log.Printf("Got %s, backing-off %d seconds", httpStatus, backoffSeconds)
		}
		err := backoffForRetry(ctx, time.Second*time.Duration(backoffSeconds))
		if err != nil {
			return nil, err
		}
		if backoffSeconds > 0 {
			backoffSeconds = 0
		}
		httpResp, errorBody, err := c.postAndParse(c.uri+path, &req, &resp)
		if err != nil {
			backoffSeconds = 10
			continue
		}
		switch {
		case httpResp.StatusCode == 200:
			done = true
		case httpResp.StatusCode == 408:
			// request timeout, retry immediately
		case httpResp.StatusCode == 503:
			// Retry
			backoffSeconds = 10
			if retryAfter := httpResp.Header.Get("Retry-After"); retryAfter != "" {
				if seconds, err := strconv.Atoi(retryAfter); err == nil {
					backoffSeconds = seconds
				}
			}
		default:
			return nil, fmt.Errorf("got HTTP Status %s: %s", httpResp.Status, errorBody)
		}
		httpStatus = httpResp.Status
	}

	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// AddChain adds the (DER represented) X509 |chain| to the log.
func (c *LogClient) AddChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddChainPath, chain)
}

// AddPreChain adds the (DER represented) Precertificate |chain| to the log.
func (c *LogClient) AddPreChain(chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(nil, AddPreChainPath, chain)
}

// AddChainWithContext adds the (DER represented) X509 |chain| to the log and
// fails if the provided context expires before the chain is submitted.
func (c *LogClient) AddChainWithContext(ctx context.Context, chain []ct.ASN1Cert) (*ct.SignedCertificateTimestamp, error) {
	return c.addChainWithRetry(ctx, AddChainPath, chain)
}

func (c *LogClient) AddJSON(data interface{}) (*ct.SignedCertificateTimestamp, error) {
	req := addJSONRequest{
		Data: data,
	}
	var resp addChainResponse
	_, _, err := c.postAndParse(c.uri+AddJSONPath, &req, &resp)
	if err != nil {
		return nil, err
	}
	rawLogID, err := base64.StdEncoding.DecodeString(resp.ID)
	if err != nil {
		return nil, err
	}
	rawSignature, err := base64.StdEncoding.DecodeString(resp.Signature)
	if err != nil {
		return nil, err
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	var logID ct.SHA256Hash
	copy(logID[:], rawLogID)
	return &ct.SignedCertificateTimestamp{
		SCTVersion: resp.SCTVersion,
		LogID:      logID,
		Timestamp:  resp.Timestamp,
		Extensions: ct.CTExtensions(resp.Extensions),
		Signature:  *ds}, nil
}

// GetSTH retrieves the current STH from the log.
// Returns a populated SignedTreeHead, or a non-nil error.
func (c *LogClient) GetSTH() (sth *ct.SignedTreeHead, err error) {
	var resp getSTHResponse
	if err = fetchAndParse(c.httpClient, c.uri+GetSTHPath, &resp); err != nil {
		return
	}
	sth = &ct.SignedTreeHead{
		TreeSize:  resp.TreeSize,
		Timestamp: resp.Timestamp,
	}

	rawRootHash, err := base64.StdEncoding.DecodeString(resp.SHA256RootHash)
	if err != nil {
		return nil, fmt.Errorf("invalid base64 encoding in sha256_root_hash: %v", err)
	}
	if len(rawRootHash) != sha256.Size {
		return nil, fmt.Errorf("sha256_root_hash is invalid length, expected %d got %d", sha256.Size, len(rawRootHash))
	}
	copy(sth.SHA256RootHash[:], rawRootHash)

	rawSignature, err := base64.StdEncoding.DecodeString(resp.TreeHeadSignature)
	if err != nil {
		return nil, errors.New("invalid base64 encoding in tree_head_signature")
	}
	ds, err := ct.UnmarshalDigitallySigned(bytes.NewReader(rawSignature))
	if err != nil {
		return nil, err
	}
	// TODO(alcutter): Verify signature
	sth.TreeHeadSignature = *ds
	return
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"go/build"
	"os"
	"regexp"
	"runtime"
	"strings"
)

func main() {
	reVer := regexp.MustCompile(`(\S+(?:\s*[+]\S+)?)`)
	reClean := regexp.MustCompile(`[^\w._]+`)
	rawVer := runtime.Version()
	m := reVer.FindStringSubmatch(rawVer)
	ver := reClean.ReplaceAllString(m[1], "..")
	env := map[string]string{
		"GOROOT":      build.Default.GOROOT,
		"GOPATH":      build.Default.GOPATH,
		"GOBIN":       os.Getenv("GOBIN"),
		"PATH":        os.Getenv("PATH"),
		"CGO_ENABLED": os.Getenv("CGO_ENABLED"),
	}

	for k, v := range env {
		if strings.TrimSpace(v) == "" {
			delete(env, k)
		}
	}

	os.Stdout.WriteString("\n")
	json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
		"RawVersion": rawVer,
		"Version":    ver,
		"Env":        env,
	})
	os.Stdout.WriteString("\n")
}
<commit_msg>sync MARGO_* env var during sh.py bootstrapping<commit_after>package main

import (
	"encoding/json"
	"go/build"
	"os"
	"regexp"
	"runtime"
	"strings"
)

func main() {
	reVer := regexp.MustCompile(`(\S+(?:\s*[+]\S+)?)`)
	reClean := regexp.MustCompile(`[^\w._]+`)
	rawVer := runtime.Version()
	m := reVer.FindStringSubmatch(rawVer)
	ver := reClean.ReplaceAllString(m[1], "..")
	env := map[string]string{
		"GOROOT":      build.Default.GOROOT,
		"GOPATH":      build.Default.GOPATH,
		"GOBIN":       os.Getenv("GOBIN"),
		"PATH":        os.Getenv("PATH"),
		"CGO_ENABLED": os.Getenv("CGO_ENABLED"),
	}
	for _, s := range os.Environ() {
		l := strings.SplitN(s, "=", 2)
		if len(l) == 2 && strings.HasPrefix(l[0], "MARGO_") {
			env[l[0]] = l[1]
		}
	}

	for k, v := range env {
		if strings.TrimSpace(v) == "" {
			delete(env, k)
		}
	}

	os.Stdout.WriteString("\n")
	json.NewEncoder(os.Stdout).Encode(map[string]interface{}{
		"RawVersion": rawVer,
		"Version":    ver,
		"Env":        env,
	})
	os.Stdout.WriteString("\n")
}
<|endoftext|>


<commit_before>package fetch

import (
	"fmt"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"
)

var (
	client *ssm.SSM
	paths  []string
	tags   []string

	trueBool = true
)

func init() {
	// initialize AWS client
	initClient()
}

func initClient() {
	session := session.Must(session.NewSession())
	client = ssm.New(session)
}

func FetchParams(paths, tags []string) ([]*ssm.Parameter, error) {
	// create tag filters
	tagFilters := make([]*ssm.ParameterStringFilter, len(tags))
	for i, tag := range tags {
		tagFilter := fmt.Sprintf("tag:%s", tag)
		tagFilters[i] = &ssm.ParameterStringFilter{
			Key: &tagFilter,
		}
	}

	// TEMP: until parameter-filters work for get-parameters-by-path (https://github.com/aws/aws-cli/issues/2850),
	// 1) retrieve parameters by tags via describe-parameters
	// 2) retrieve parameters by path via get-parameters-by-path
	// 3) calculate union of two sets

	// retrieve all parameters with given tags
	paramNames, err := describeParams(tagFilters)
	if err != nil {
		return nil, err
	}

	// retrieve params for given paths
	params, err := getParamsByPath(paths)
	if err != nil {
		return params, err
	}

	// calculate union of two sets
	union := calcUnion(paramNames, params)

	return union, nil
}

func describeParams(filters []*ssm.ParameterStringFilter) ([]string, error) {
	paramNames := make([]string, 0)

	done := false
	var nextToken string
	for !done {
		input := &ssm.DescribeParametersInput{
			ParameterFilters: filters,
		}

		if nextToken != "" {
			input.SetNextToken(nextToken)
		}

		output, err := client.DescribeParameters(input)
		if err != nil {
			return paramNames, err
		}

		for _, param := range output.Parameters {
			paramNames = append(paramNames, *param.Name)
		}

		// there are more parameters if nextToken is given in response
		if output.NextToken != nil {
			nextToken = *output.NextToken
		} else {
			done = true
		}
	}

	return paramNames, nil
}

func getParamsByPath(paths []string) ([]*ssm.Parameter, error) {
	params := make([]*ssm.Parameter, 0)

	// retrieve params for all paths
	for _, path := range paths {

		done := false
		var nextToken string
		for !done {
			input := &ssm.GetParametersByPathInput{
				Path:           &path,
				Recursive:      &trueBool,
				WithDecryption: &trueBool,
			}

			if nextToken != "" {
				input.SetNextToken(nextToken)
			}

			output, err := client.GetParametersByPath(input)
			if err != nil {
				return params, err
			}

			params = append(params, output.Parameters...)

			// there are more parameters for this path if nextToken is given in response
			if output.NextToken != nil {
				nextToken = *output.NextToken
			} else {
				done = true
			}
		}
	}

	return params, nil
}

func calcUnion(paramNames []string, params []*ssm.Parameter) []*ssm.Parameter {
	// build map lookup
	lookup := make(map[string]bool)
	for _, paramName := range paramNames {
		lookup[paramName] = true
	}

	// calculate union
	union := make([]*ssm.Parameter, 0)
	for _, param := range params {
		if lookup[*param.Name] {
			union = append(union, param)
		}
	}

	return union
}

func getParamNameValues(params []*ssm.Parameter) map[string]string {
	paramVals := make(map[string]string, len(params))
	for _, param := range params {
		split := strings.Split(*param.Name, "/")
		name := split[len(split)-1]
		paramVals[strings.ToUpper(name)] = *param.Value
	}
	return paramVals
}

func MustSetOS(paths, tags []string) {
	params, err := FetchParams(paths, tags)
	if err != nil {
		panic(err)
	}
	nameValues := getParamNameValues(params)
	for name, value := range nameValues {
		if err := os.Setenv(name, value); err != nil{
			panic(err)
		}
	}
}

<commit_msg>init clients in init func<commit_after>package fetch

import (
	"fmt"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"
)

var (
	client *ssm.SSM
	paths  []string
	tags   []string

	trueBool = true
)

func init() {
	session := session.Must(session.NewSession())
	client = ssm.New(session)
}

func FetchParams(paths, tags []string) ([]*ssm.Parameter, error) {
	// create tag filters
	tagFilters := make([]*ssm.ParameterStringFilter, len(tags))
	for i, tag := range tags {
		tagFilter := fmt.Sprintf("tag:%s", tag)
		tagFilters[i] = &ssm.ParameterStringFilter{
			Key: &tagFilter,
		}
	}

	// TEMP: until parameter-filters work for get-parameters-by-path (https://github.com/aws/aws-cli/issues/2850),
	// 1) retrieve parameters by tags via describe-parameters
	// 2) retrieve parameters by path via get-parameters-by-path
	// 3) calculate union of two sets

	// retrieve all parameters with given tags
	paramNames, err := describeParams(tagFilters)
	if err != nil {
		return nil, err
	}

	// retrieve params for given paths
	params, err := getParamsByPath(paths)
	if err != nil {
		return params, err
	}

	// calculate union of two sets
	union := calcUnion(paramNames, params)

	return union, nil
}

func describeParams(filters []*ssm.ParameterStringFilter) ([]string, error) {
	paramNames := make([]string, 0)

	done := false
	var nextToken string
	for !done {
		input := &ssm.DescribeParametersInput{
			ParameterFilters: filters,
		}

		if nextToken != "" {
			input.SetNextToken(nextToken)
		}

		output, err := client.DescribeParameters(input)
		if err != nil {
			return paramNames, err
		}

		for _, param := range output.Parameters {
			paramNames = append(paramNames, *param.Name)
		}

		// there are more parameters if nextToken is given in response
		if output.NextToken != nil {
			nextToken = *output.NextToken
		} else {
			done = true
		}
	}

	return paramNames, nil
}

func getParamsByPath(paths []string) ([]*ssm.Parameter, error) {
	params := make([]*ssm.Parameter, 0)

	// retrieve params for all paths
	for _, path := range paths {

		done := false
		var nextToken string
		for !done {
			input := &ssm.GetParametersByPathInput{
				Path:           &path,
				Recursive:      &trueBool,
				WithDecryption: &trueBool,
			}

			if nextToken != "" {
				input.SetNextToken(nextToken)
			}

			output, err := client.GetParametersByPath(input)
			if err != nil {
				return params, err
			}

			params = append(params, output.Parameters...)

			// there are more parameters for this path if nextToken is given in response
			if output.NextToken != nil {
				nextToken = *output.NextToken
			} else {
				done = true
			}
		}
	}

	return params, nil
}

func calcUnion(paramNames []string, params []*ssm.Parameter) []*ssm.Parameter {
	// build map lookup
	lookup := make(map[string]bool)
	for _, paramName := range paramNames {
		lookup[paramName] = true
	}

	// calculate union
	union := make([]*ssm.Parameter, 0)
	for _, param := range params {
		if lookup[*param.Name] {
			union = append(union, param)
		}
	}

	return union
}

func getParamNameValues(params []*ssm.Parameter) map[string]string {
	paramVals := make(map[string]string, len(params))
	for _, param := range params {
		split := strings.Split(*param.Name, "/")
		name := split[len(split)-1]
		paramVals[strings.ToUpper(name)] = *param.Value
	}
	return paramVals
}

func MustSetOS(paths, tags []string) {
	params, err := FetchParams(paths, tags)
	if err != nil {
		panic(err)
	}
	nameValues := getParamNameValues(params)
	for name, value := range nameValues {
		if err := os.Setenv(name, value); err != nil{
			panic(err)
		}
	}
}

<|endoftext|>


<commit_before>package films

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/fzzy/radix/redis"
)

type Film struct {
	Adult             bool `json:"adult"`
	AlternativeTitles struct {
		Titles []struct {
			Iso31661 string `json:"iso_3166_1"`
			Title    string `json:"title"`
		} `json:"titles"`
	} `json:"alternative_titles"`
	Availability        *FilmAvailability `json:"availability,omitempty"`
	BackdropPath        string            `json:"backdrop_path"`
	BelongsToCollection struct {
		BackdropPath string `json:"backdrop_path"`
		ID           int    `json:"id"`
		Name         string `json:"name"`
		PosterPath   string `json:"poster_path"`
	} `json:"belongs_to_collection"`
	Budget  int `json:"budget"`
	Credits struct {
		Cast []struct {
			CastID      int    `json:"cast_id"`
			Character   string `json:"character"`
			CreditID    string `json:"credit_id"`
			ID          int    `json:"id"`
			Name        string `json:"name"`
			Order       int    `json:"order"`
			ProfilePath string `json:"profile_path"`
		} `json:"cast"`
		Crew []struct {
			CreditID    string `json:"credit_id"`
			Department  string `json:"department"`
			ID          int    `json:"id"`
			Job         string `json:"job"`
			Name        string `json:"name"`
			ProfilePath string `json:"profile_path"`
		} `json:"crew"`
	} `json:"credits"`
	Genres []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"genres"`
	Homepage string `json:"homepage"`
	ID       int    `json:"id"`
	Images   struct {
		Backdrops []struct {
			AspectRatio float64     `json:"aspect_ratio"`
			FilePath    string      `json:"file_path"`
			Height      int         `json:"height"`
			Iso6391     interface{} `json:"iso_639_1"`
			VoteAverage float64     `json:"vote_average"`
			VoteCount   int         `json:"vote_count"`
			Width       int         `json:"width"`
		} `json:"backdrops"`
		Posters []struct {
			AspectRatio float64 `json:"aspect_ratio"`
			FilePath    string  `json:"file_path"`
			Height      int     `json:"height"`
			ID          string  `json:"id"`
			Iso6391     string  `json:"iso_639_1"`
			VoteAverage float64 `json:"vote_average"`
			VoteCount   int     `json:"vote_count"`
			Width       int     `json:"width"`
		} `json:"posters"`
	} `json:"images"`
	ImdbID   string `json:"imdb_id"`
	Keywords struct {
		Keywords []struct {
			ID   int    `json:"id"`
			Name string `json:"name"`
		} `json:"keywords"`
	} `json:"keywords"`
	OriginalLanguage    string  `json:"original_language"`
	OriginalTitle       string  `json:"original_title"`
	Overview            string  `json:"overview"`
	Popularity          float64 `json:"popularity"`
	PosterPath          string  `json:"poster_path"`
	ProductionCompanies []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"production_companies"`
	ProductionCountries []struct {
		Iso31661 string `json:"iso_3166_1"`
		Name     string `json:"name"`
	} `json:"production_countries"`
	ReleaseDate string `json:"release_date"`
	Releases    struct {
		Countries []struct {
			Certification string `json:"certification"`
			Iso31661      string `json:"iso_3166_1"`
			Primary       bool   `json:"primary"`
			ReleaseDate   string `json:"release_date"`
		} `json:"countries"`
	} `json:"releases"`
	Revenue int `json:"revenue"`
	Runtime int `json:"runtime"`
	Similar struct {
		Page    int `json:"page"`
		Results []struct {
			Adult         bool    `json:"adult"`
			BackdropPath  string  `json:"backdrop_path"`
			ID            int     `json:"id"`
			OriginalTitle string  `json:"original_title"`
			Popularity    float64 `json:"popularity"`
			PosterPath    string  `json:"poster_path"`
			ReleaseDate   string  `json:"release_date"`
			Title         string  `json:"title"`
			Video         bool    `json:"video"`
			VoteAverage   float64 `json:"vote_average"`
			VoteCount     int     `json:"vote_count"`
		} `json:"results"`
		TotalPages   int `json:"total_pages"`
		TotalResults int `json:"total_results"`
	} `json:"similar"`
	SpokenLanguages []struct {
		Iso6391 string `json:"iso_639_1"`
		Name    string `json:"name"`
	} `json:"spoken_languages"`
	Status  string `json:"status"`
	Tagline string `json:"tagline"`
	Title   string `json:"title"`
	Video   bool   `json:"video"`
	Videos  struct {
		Results []struct {
			ID      string `json:"id"`
			Iso6391 string `json:"iso_639_1"`
			Key     string `json:"key"`
			Name    string `json:"name"`
			Site    string `json:"site"`
			Size    int    `json:"size"`
			Type    string `json:"type"`
		} `json:"results"`
	} `json:"videos"`
	VoteAverage float64 `json:"vote_average"`
	VoteCount   int     `json:"vote_count"`
}

type FilmAvailability struct {
	Purchase *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_purchase,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_purchase,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_purchase,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_purchase,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_purchase,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_purchase,omitempty"`
		XBOXMarketplace *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"xbox_purchase,omitempty"`
	} `json:"purchase,omitempty"`
	Rental *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_rental,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_rental,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_rental,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_rental,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_rental,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_rental,omitempty"`
	} `json:"rental,omitempty"`
	Streaming *struct {
		NetflixInstant *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"netflix_instant,omitempty"`
	} `json:"streaming,omitempty"`
}

var (
	api_key string
)

func init() {
	api_key = os.Getenv("TMDB_API_KEY")

	if api_key == "" {
		log.Fatal("Missing API key")
	}
}

func Get(film_id string) (Film, error) {
	var film Film

	client, err := redis.Dial("tcp", "localhost:6379")

	if err != nil {
		log.Fatal(err)
	}

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		log.Fatal(err)
	}

	s, err := client.Cmd("get", film_id).Str()

	if err == nil {
		err = json.Unmarshal([]byte(s), &film)

		if err == nil {
			return film, nil
		}
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return film, err
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		_ = client.Cmd("set", film_id, marshalled)
	}

	return film, nil
}

func Prepare(film_id string) {
	var film Film

	client, err := redis.Dial("tcp", "localhost:6379")

	if err != nil {
		return
	}

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		return
	}

	_, err = client.Cmd("get", film_id).Str()

	if err == nil {
		return
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		_ = client.Cmd("set", film_id, marshalled)
	}

	return
}

func queryTMDb(film_id string) (Film, error) {
	var url string
	var film Film

	var buffer bytes.Buffer

	buffer.WriteString("http://api.themoviedb.org/3/movie/")
	buffer.WriteString(film_id)
	buffer.WriteString("?api_key=")
	buffer.WriteString(api_key)
	buffer.WriteString("&append_to_response=")
	buffer.WriteString("alternative_titles,credits,images,keywords,releases,videos,similar")

	url = string(buffer.Bytes())

	resp, err := http.Get(url)

	if err != nil {
		return film, err
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return film, err
	}

	err = json.Unmarshal(body, &film)

	if err != nil {
		return film, err
	}

	return film, nil
}

func determineAvailability(film Film) (FilmAvailability, error) {
	type searchResult struct {
		Id    string `json:"_id"`
		Links struct {
			IMDb string `json:"imdb"`
		} `json:"links"`
	}

	var availability FilmAvailability
	var results []searchResult

	var cisi_url *url.URL

	cisi_url, err := url.Parse("http://www.canistream.it/services/search")

	if err != nil {
		return availability, err
	}

	parameters := url.Values{}
	parameters.Add("movieName", film.Title)

	cisi_url.RawQuery = parameters.Encode()

	resp, err := http.Get(cisi_url.String())

	if err != nil {
		return availability, err
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return availability, err
	}

	err = json.Unmarshal(body, &results)

	if err != nil {
		return availability, err
	}

	var canIStreamItID string

	for _, result := range results {
		var imdb_id string

		if result.Links.IMDb != "" {
			imdb_id = strings.Split(result.Links.IMDb, "/")[4]
		}

		if imdb_id == film.ImdbID {
			canIStreamItID = result.Id
		}
	}

	if canIStreamItID != "" {
		mediaTypes := []string{"rental", "purchase", "streaming"}

		for _, mediaType := range mediaTypes {
			var buffer bytes.Buffer

			buffer.WriteString("http://www.canistream.it/services/query?movieId=")
			buffer.WriteString(canIStreamItID)
			buffer.WriteString("&attributes=1&mediaType=")
			buffer.WriteString(mediaType)

			resp, err = http.Get(string(buffer.Bytes()))

			if err != nil {
				return availability, err
			}

			defer resp.Body.Close()

			body, err = ioutil.ReadAll(resp.Body)

			if err != nil {
				return availability, err
			}

			switch mediaType {
			case "rental":
				err = json.Unmarshal(body, &availability.Rental)

				if err != nil {
					return availability, err
				}
			case "purchase":
				err = json.Unmarshal(body, &availability.Purchase)

				if err != nil {
					return availability, err
				}
			case "streaming":
				err = json.Unmarshal(body, &availability.Streaming)

				if err != nil {
					return availability, err
				}
			}
		}
	}

	return availability, nil
}
<commit_msg>Set a TTL of 3600 seconds for items cached in Redis<commit_after>package films

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/fzzy/radix/redis"
)

type Film struct {
	Adult             bool `json:"adult"`
	AlternativeTitles struct {
		Titles []struct {
			Iso31661 string `json:"iso_3166_1"`
			Title    string `json:"title"`
		} `json:"titles"`
	} `json:"alternative_titles"`
	Availability        *FilmAvailability `json:"availability,omitempty"`
	BackdropPath        string            `json:"backdrop_path"`
	BelongsToCollection struct {
		BackdropPath string `json:"backdrop_path"`
		ID           int    `json:"id"`
		Name         string `json:"name"`
		PosterPath   string `json:"poster_path"`
	} `json:"belongs_to_collection"`
	Budget  int `json:"budget"`
	Credits struct {
		Cast []struct {
			CastID      int    `json:"cast_id"`
			Character   string `json:"character"`
			CreditID    string `json:"credit_id"`
			ID          int    `json:"id"`
			Name        string `json:"name"`
			Order       int    `json:"order"`
			ProfilePath string `json:"profile_path"`
		} `json:"cast"`
		Crew []struct {
			CreditID    string `json:"credit_id"`
			Department  string `json:"department"`
			ID          int    `json:"id"`
			Job         string `json:"job"`
			Name        string `json:"name"`
			ProfilePath string `json:"profile_path"`
		} `json:"crew"`
	} `json:"credits"`
	Genres []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"genres"`
	Homepage string `json:"homepage"`
	ID       int    `json:"id"`
	Images   struct {
		Backdrops []struct {
			AspectRatio float64     `json:"aspect_ratio"`
			FilePath    string      `json:"file_path"`
			Height      int         `json:"height"`
			Iso6391     interface{} `json:"iso_639_1"`
			VoteAverage float64     `json:"vote_average"`
			VoteCount   int         `json:"vote_count"`
			Width       int         `json:"width"`
		} `json:"backdrops"`
		Posters []struct {
			AspectRatio float64 `json:"aspect_ratio"`
			FilePath    string  `json:"file_path"`
			Height      int     `json:"height"`
			ID          string  `json:"id"`
			Iso6391     string  `json:"iso_639_1"`
			VoteAverage float64 `json:"vote_average"`
			VoteCount   int     `json:"vote_count"`
			Width       int     `json:"width"`
		} `json:"posters"`
	} `json:"images"`
	ImdbID   string `json:"imdb_id"`
	Keywords struct {
		Keywords []struct {
			ID   int    `json:"id"`
			Name string `json:"name"`
		} `json:"keywords"`
	} `json:"keywords"`
	OriginalLanguage    string  `json:"original_language"`
	OriginalTitle       string  `json:"original_title"`
	Overview            string  `json:"overview"`
	Popularity          float64 `json:"popularity"`
	PosterPath          string  `json:"poster_path"`
	ProductionCompanies []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"production_companies"`
	ProductionCountries []struct {
		Iso31661 string `json:"iso_3166_1"`
		Name     string `json:"name"`
	} `json:"production_countries"`
	ReleaseDate string `json:"release_date"`
	Releases    struct {
		Countries []struct {
			Certification string `json:"certification"`
			Iso31661      string `json:"iso_3166_1"`
			Primary       bool   `json:"primary"`
			ReleaseDate   string `json:"release_date"`
		} `json:"countries"`
	} `json:"releases"`
	Revenue int `json:"revenue"`
	Runtime int `json:"runtime"`
	Similar struct {
		Page    int `json:"page"`
		Results []struct {
			Adult         bool    `json:"adult"`
			BackdropPath  string  `json:"backdrop_path"`
			ID            int     `json:"id"`
			OriginalTitle string  `json:"original_title"`
			Popularity    float64 `json:"popularity"`
			PosterPath    string  `json:"poster_path"`
			ReleaseDate   string  `json:"release_date"`
			Title         string  `json:"title"`
			Video         bool    `json:"video"`
			VoteAverage   float64 `json:"vote_average"`
			VoteCount     int     `json:"vote_count"`
		} `json:"results"`
		TotalPages   int `json:"total_pages"`
		TotalResults int `json:"total_results"`
	} `json:"similar"`
	SpokenLanguages []struct {
		Iso6391 string `json:"iso_639_1"`
		Name    string `json:"name"`
	} `json:"spoken_languages"`
	Status  string `json:"status"`
	Tagline string `json:"tagline"`
	Title   string `json:"title"`
	Video   bool   `json:"video"`
	Videos  struct {
		Results []struct {
			ID      string `json:"id"`
			Iso6391 string `json:"iso_639_1"`
			Key     string `json:"key"`
			Name    string `json:"name"`
			Site    string `json:"site"`
			Size    int    `json:"size"`
			Type    string `json:"type"`
		} `json:"results"`
	} `json:"videos"`
	VoteAverage float64 `json:"vote_average"`
	VoteCount   int     `json:"vote_count"`
}

type FilmAvailability struct {
	Purchase *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_purchase,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_purchase,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_purchase,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_purchase,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_purchase,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_purchase,omitempty"`
		XBOXMarketplace *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"xbox_purchase,omitempty"`
	} `json:"purchase,omitempty"`
	Rental *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_rental,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_rental,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_rental,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_rental,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_rental,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_rental,omitempty"`
	} `json:"rental,omitempty"`
	Streaming *struct {
		NetflixInstant *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"netflix_instant,omitempty"`
	} `json:"streaming,omitempty"`
}

var (
	api_key string
)

func init() {
	api_key = os.Getenv("TMDB_API_KEY")

	if api_key == "" {
		log.Fatal("Missing API key")
	}
}

func Get(film_id string) (Film, error) {
	var film Film

	client, err := redis.Dial("tcp", "localhost:6379")

	if err != nil {
		log.Fatal(err)
	}

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		log.Fatal(err)
	}

	s, err := client.Cmd("get", film_id).Str()

	if err == nil {
		err = json.Unmarshal([]byte(s), &film)

		if err == nil {
			return film, nil
		}
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return film, err
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		r := client.Cmd("set", film_id, marshalled)
		if r.Err == nil {
			_ = client.Cmd("expire", film_id, 3600)
		}
	}

	return film, nil
}

func Prepare(film_id string) {
	var film Film

	client, err := redis.Dial("tcp", "localhost:6379")

	if err != nil {
		return
	}

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		return
	}

	_, err = client.Cmd("get", film_id).Str()

	if err == nil {
		return
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		r := client.Cmd("set", film_id, marshalled)
		if r.Err == nil {
			_ = client.Cmd("expire", film_id, 3600)
		}
	}

	return
}

func queryTMDb(film_id string) (Film, error) {
	var url string
	var film Film

	var buffer bytes.Buffer

	buffer.WriteString("http://api.themoviedb.org/3/movie/")
	buffer.WriteString(film_id)
	buffer.WriteString("?api_key=")
	buffer.WriteString(api_key)
	buffer.WriteString("&append_to_response=")
	buffer.WriteString("alternative_titles,credits,images,keywords,releases,videos,similar")

	url = string(buffer.Bytes())

	resp, err := http.Get(url)

	if err != nil {
		return film, err
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return film, err
	}

	err = json.Unmarshal(body, &film)

	if err != nil {
		return film, err
	}

	return film, nil
}

func determineAvailability(film Film) (FilmAvailability, error) {
	type searchResult struct {
		Id    string `json:"_id"`
		Links struct {
			IMDb string `json:"imdb"`
		} `json:"links"`
	}

	var availability FilmAvailability
	var results []searchResult

	var cisi_url *url.URL

	cisi_url, err := url.Parse("http://www.canistream.it/services/search")

	if err != nil {
		return availability, err
	}

	parameters := url.Values{}
	parameters.Add("movieName", film.Title)

	cisi_url.RawQuery = parameters.Encode()

	resp, err := http.Get(cisi_url.String())

	if err != nil {
		return availability, err
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)

	if err != nil {
		return availability, err
	}

	err = json.Unmarshal(body, &results)

	if err != nil {
		return availability, err
	}

	var canIStreamItID string

	for _, result := range results {
		var imdb_id string

		if result.Links.IMDb != "" {
			imdb_id = strings.Split(result.Links.IMDb, "/")[4]
		}

		if imdb_id == film.ImdbID {
			canIStreamItID = result.Id
		}
	}

	if canIStreamItID != "" {
		mediaTypes := []string{"rental", "purchase", "streaming"}

		for _, mediaType := range mediaTypes {
			var buffer bytes.Buffer

			buffer.WriteString("http://www.canistream.it/services/query?movieId=")
			buffer.WriteString(canIStreamItID)
			buffer.WriteString("&attributes=1&mediaType=")
			buffer.WriteString(mediaType)

			resp, err = http.Get(string(buffer.Bytes()))

			if err != nil {
				return availability, err
			}

			defer resp.Body.Close()

			body, err = ioutil.ReadAll(resp.Body)

			if err != nil {
				return availability, err
			}

			switch mediaType {
			case "rental":
				err = json.Unmarshal(body, &availability.Rental)

				if err != nil {
					return availability, err
				}
			case "purchase":
				err = json.Unmarshal(body, &availability.Purchase)

				if err != nil {
					return availability, err
				}
			case "streaming":
				err = json.Unmarshal(body, &availability.Streaming)

				if err != nil {
					return availability, err
				}
			}
		}
	}

	return availability, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Benoit Caron and contributors
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

/*
Package sensu implements simple methods to interact with Sensu API.

Usage:
	sensu := New("Sensu test API", "", "http://your.SENSU_SERVER_URL.tld"), 15, "username", "secret")
	events = sensu.GetEvents()

The sensu object expose methods that corresponds to the Sensu API:
 http://sensuapp.org/docs/0.13/api_overview

The package follow the form GetApiName() or GetApiName(args, args) where the api
 call wants arguments.

Eg:
	// match http://sensu/clients/
	clients, err := sensu.GetClients()
	// match http://sensu/clients/my_hostname
	a_client,err  := sensu.GetClient("my_hostname")
	// match http://sensu/clients/my_hostname/history
	a_history,err := sensu.GetClientHistory("my_hostname")

Methods returns a data structure and an error object that is nil on successfull query.

API methods that returns an array of results will return a []interface{}.
Methods that return a JSON object will return a map[string]interface{}.
Methods that returns no results on success will return nil, or non-nil (an error object) on errors

No distinction is made yet between server errors (HTTP Status 500) and missing
objects (HTTP status 404)

*/
package sensu

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
	//"bytes"
)

// Sensu struct contains the API details
type Sensu struct {
	Name    string
	Path    string
	URL     string
	Timeout int
	User    string
	Pass    string
}

// NoLimit do not specify a limit parameter
const NoLimit int = -1

// NoOffset do not specify an offset parameter
const NoOffset int = -1

// New Initialize a new Sensu API
func New(name string, path string, url string, timeout int, username string, password string) *Sensu {
	return &Sensu{name, path, url, timeout, username, password}
}

// Health The health endpoint checks to see if the api can connect to redis and rabbitmq. It takes parameters for minimum consumers and maximum messages and checks rabbitmq.
func (s *Sensu) Health(consumers int, messages int) (map[string]interface{}, error) {
	return s.get(fmt.Sprintf("health/%d/%d", consumers, messages))
}

// Info Will return the Sensu version along with rabbitmq and redis information.
func (s *Sensu) Info() (map[string]interface{}, error) {
	return s.get("info")
}

// get ...
func (s *Sensu) get(endpoint string) (map[string]interface{}, error) {
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("Parsing error: %q returned: %v", err, err)
	}
	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSON(res)
}

// getList Construct an API call and return the list of results
//  LIMITATION: the limit/offset is currently ignored and all results are sent back
func (s *Sensu) getList(endpoint string, limit int, offset int) ([]interface{}, error) {

	/*
		args := ""
		//ERROR GET LIST TODO deal with limit %d and offset %d", limit, offset
		if limit != NOLIMIT {
			args = fmt.Sprintf("%slimit=%d", args, limit)
		}
		if offset != NOOFFSET {
			args = fmt.Sprintf("%soffset=%d", args, limit)
		}
	*/
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("URL Parsing error: %q returned: %v", url, err)
	}
	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSONArray(res)
}

func (s *Sensu) doHTTP(req *http.Request) ([]byte, error) {

	if s.User != "" && s.Pass != "" {
		req.SetBasicAuth(s.User, s.Pass)
	}

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}

	client := http.Client{Timeout: time.Duration(s.Timeout) * time.Second, Transport: tr}
	res, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("%v", err)
	}
	if res.StatusCode >= 400 {
		return nil, fmt.Errorf("%v", res.Status)
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("Parsing response body returned: %v", err)
	}
	return body, nil
}

// doJsonArray Unmarshall JSON expecting an array
func (s *Sensu) doJSONArray(body []byte) ([]interface{}, error) {
	var results []interface{}
	if err := json.Unmarshal(body, &results); err != nil {
		return nil, fmt.Errorf("Parsing JSON-encoded response body: %v", err)
	}
	return results, nil
}

// doJsonArray Unmarshall JSON expecting a map
func (s *Sensu) doJSON(body []byte) (map[string]interface{}, error) {
	var results map[string]interface{}
	if err := json.Unmarshal(body, &results); err != nil {
		return nil, fmt.Errorf("Parsing JSON-encoded response body: %v", err)
	}
	return results, nil
}

// Post to endpoint
func (s *Sensu) post(endpoint string) (map[string]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0

	//ERROR GET LIST TODO deal with limit %d and offset %d", limit, offset

	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return nil, fmt.Errorf("Parsing error: %q returned: %v", url, err)
	}

	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSON(res)
}

// postPayload to endpoint
func (s *Sensu) postPayload(endpoint string, payload string) (map[string]interface{}, error) {
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)

	req, err := http.NewRequest("POST", url, strings.NewReader(fmt.Sprintf("%s\n\n", payload)))

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Content-Length", fmt.Sprintf("%d", len(payload)))

	if err != nil {
		return nil, fmt.Errorf("Parsing error: %q returned: %v", url, err)
	}
	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSON(res)
}

// Delete resource
func (s *Sensu) delete(endpoint string) error {
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return fmt.Errorf("Parsing error: %q returned: %v", err, err)
	}

	res, err := http.DefaultClient.Do(req)
	defer res.Body.Close()
	if err != nil {
		return fmt.Errorf("API call to %q returned: %v", url, err)
	}

	if err != nil {
		return fmt.Errorf("%v", err)
	}
	if res.StatusCode >= 400 {
		return fmt.Errorf("%v", res.Status)
	}
	return nil
}
<commit_msg>Insecure connection to Sensu API should be an option<commit_after>// Copyright 2014 Benoit Caron and contributors
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

/*
Package sensu implements simple methods to interact with Sensu API.

Usage:
	sensu := New("Sensu test API", "", "http://your.SENSU_SERVER_URL.tld"), 15, "username", "secret")
	events = sensu.GetEvents()

The sensu object expose methods that corresponds to the Sensu API:
 http://sensuapp.org/docs/0.13/api_overview

The package follow the form GetApiName() or GetApiName(args, args) where the api
 call wants arguments.

Eg:
	// match http://sensu/clients/
	clients, err := sensu.GetClients()
	// match http://sensu/clients/my_hostname
	a_client,err  := sensu.GetClient("my_hostname")
	// match http://sensu/clients/my_hostname/history
	a_history,err := sensu.GetClientHistory("my_hostname")

Methods returns a data structure and an error object that is nil on successfull query.

API methods that returns an array of results will return a []interface{}.
Methods that return a JSON object will return a map[string]interface{}.
Methods that returns no results on success will return nil, or non-nil (an error object) on errors

No distinction is made yet between server errors (HTTP Status 500) and missing
objects (HTTP status 404)

*/
package sensu

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
	//"bytes"
)

// Sensu struct contains the API details
type Sensu struct {
	Name     string
	Path     string
	URL      string
	Timeout  int
	User     string
	Pass     string
	Insecure bool
}

// NoLimit do not specify a limit parameter
const NoLimit int = -1

// NoOffset do not specify an offset parameter
const NoOffset int = -1

// New Initialize a new Sensu API
func New(name string, path string, url string, timeout int, username string, password string, insecure bool) *Sensu {
	return &Sensu{name, path, url, timeout, username, password, insecure}
}

// Health The health endpoint checks to see if the api can connect to redis and rabbitmq. It takes parameters for minimum consumers and maximum messages and checks rabbitmq.
func (s *Sensu) Health(consumers int, messages int) (map[string]interface{}, error) {
	return s.get(fmt.Sprintf("health/%d/%d", consumers, messages))
}

// Info Will return the Sensu version along with rabbitmq and redis information.
func (s *Sensu) Info() (map[string]interface{}, error) {
	return s.get("info")
}

// get ...
func (s *Sensu) get(endpoint string) (map[string]interface{}, error) {
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("Parsing error: %q returned: %v", err, err)
	}
	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSON(res)
}

// getList Construct an API call and return the list of results
//  LIMITATION: the limit/offset is currently ignored and all results are sent back
func (s *Sensu) getList(endpoint string, limit int, offset int) ([]interface{}, error) {

	/*
		args := ""
		//ERROR GET LIST TODO deal with limit %d and offset %d", limit, offset
		if limit != NOLIMIT {
			args = fmt.Sprintf("%slimit=%d", args, limit)
		}
		if offset != NOOFFSET {
			args = fmt.Sprintf("%soffset=%d", args, limit)
		}
	*/
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("URL Parsing error: %q returned: %v", url, err)
	}
	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSONArray(res)
}

func (s *Sensu) doHTTP(req *http.Request) ([]byte, error) {

	if s.User != "" && s.Pass != "" {
		req.SetBasicAuth(s.User, s.Pass)
	}

	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: s.Insecure},
	}

	client := http.Client{Timeout: time.Duration(s.Timeout) * time.Second, Transport: tr}
	res, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("%v", err)
	}
	if res.StatusCode >= 400 {
		return nil, fmt.Errorf("%v", res.Status)
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("Parsing response body returned: %v", err)
	}
	return body, nil
}

// doJsonArray Unmarshall JSON expecting an array
func (s *Sensu) doJSONArray(body []byte) ([]interface{}, error) {
	var results []interface{}
	if err := json.Unmarshal(body, &results); err != nil {
		return nil, fmt.Errorf("Parsing JSON-encoded response body: %v", err)
	}
	return results, nil
}

// doJsonArray Unmarshall JSON expecting a map
func (s *Sensu) doJSON(body []byte) (map[string]interface{}, error) {
	var results map[string]interface{}
	if err := json.Unmarshal(body, &results); err != nil {
		return nil, fmt.Errorf("Parsing JSON-encoded response body: %v", err)
	}
	return results, nil
}

// Post to endpoint
func (s *Sensu) post(endpoint string) (map[string]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0

	//ERROR GET LIST TODO deal with limit %d and offset %d", limit, offset

	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return nil, fmt.Errorf("Parsing error: %q returned: %v", url, err)
	}

	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSON(res)
}

// postPayload to endpoint
func (s *Sensu) postPayload(endpoint string, payload string) (map[string]interface{}, error) {
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)

	req, err := http.NewRequest("POST", url, strings.NewReader(fmt.Sprintf("%s\n\n", payload)))

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Content-Length", fmt.Sprintf("%d", len(payload)))

	if err != nil {
		return nil, fmt.Errorf("Parsing error: %q returned: %v", url, err)
	}
	res, err := s.doHTTP(req)
	if err != nil {
		return nil, fmt.Errorf("API call to %q returned: %v", url, err)
	}
	return s.doJSON(res)
}

// Delete resource
func (s *Sensu) delete(endpoint string) error {
	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("DELETE", url, nil)
	if err != nil {
		return fmt.Errorf("Parsing error: %q returned: %v", err, err)
	}

	res, err := http.DefaultClient.Do(req)
	defer res.Body.Close()
	if err != nil {
		return fmt.Errorf("API call to %q returned: %v", url, err)
	}

	if err != nil {
		return fmt.Errorf("%v", err)
	}
	if res.StatusCode >= 400 {
		return fmt.Errorf("%v", res.Status)
	}
	return nil
}
<|endoftext|>


<commit_before>// Package sensu implements simple methods to interact with Sensu API.
package sensu

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

// Sensu struct contains the API details
type Sensu struct {
	Name    string
	Path    string
	URL     string
	Timeout int
}

// Sensu contains data fetched from Sensu API
//var sensu = new(Sensu)

// NewSensu initialize a new Sensu API
func (s *Sensu) NewSensu() {
	fmt.Printf("NewSensu %+v\n", s)
	//s.Config = config
	//fmt.Printf("%+v\n", config)
	//fmt.Printf("%+v\n", s)
	//if s.Name = config.Name; s.Name != "" {
	//  s.Name
	//}
	//s = config
	///fmt.Printf("%+v\n", s)
}

// Health The health endpoint checks to see if the api can connect to redis and rabbitmq. It takes parameters for minimum consumers and maximum messages and checks rabbitmq.
func (s *Sensu) Health(consumers int, messages int) ([]interface{}, error) {
	fmt.Printf("FIXME health args")
	return s.Get(fmt.Sprintf("health/%d/%d", consumers, messages))
}

// Info Will return the Sensu version along with rabbitmq and redis information.
func (s *Sensu) Info() ([]interface{}, error) {
	return s.Get("info")
}

// Delete Delete resource
func (s *Sensu) Delete(endpoint string) ([]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0
	fmt.Printf("FIXME Delete is NOT IMPLEMENTED")
	return s.GetList(endpoint, 0, 0)
}

// Get ...
func (s *Sensu) Get(endpoint string) ([]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0
	return s.GetList(endpoint, 0, 0)
}

// GetList Construct an API call and return the list of results
func (s *Sensu) GetList(endpoint string, limit int, offset int) ([]interface{}, error) {

	fmt.Printf("ERROR GET LIST TODO deal with limit %d and offset %d", limit, offset)

	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("Sensu.Get URL Parsing error: %q returned: %v", err, err)
	}

	client := http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("HTTP request returned: %v", err)
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("Parsing response body returned: %v", err)
	}

	var results []interface{}
	if err := json.Unmarshal(body, &results); err != nil {
		return nil, fmt.Errorf("Parsing JSON-encoded response body: %v", err)
	}

	return results, nil
}

// Post to endpoint
func (s *Sensu) Post(endpoint string) ([]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0
	fmt.Printf("POST is not implemented yet\n")
	return s.Get(endpoint)
}

// func init() {
// 	fmt.Printf("why is this called?")
// 	sensu.Name = "Sensu"
// 	sensu.Path = ""
// 	sensu.Timeout = 10000
// }

//func GetClients(url string) ([]interface{}, error) {
//clients, err := get(url, "clients")
//if err != nil {
//  return nil, fmt.Errorf("%v", err)
//}
//return clients, nil
//else {
//for _, data := range clients {
//  client := data.(map[string]interface{})
//name := client["name"]
//  fmt.Printf("%v\n", client)
//  }
//}
//}
<commit_msg>Use NewSensu as constructor<commit_after>// Package sensu implements simple methods to interact with Sensu API.
package sensu

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

// Sensu struct contains the API details
type Sensu struct {
	Name    string
	Path    string
	URL     string
	Timeout int
}

// NewSensu initialize a new Sensu API
func NewSensu(name string, path string, url string, timeout int) *Sensu {
	return &Sensu{name, path, url, timeout}
}

// Health The health endpoint checks to see if the api can connect to redis and rabbitmq. It takes parameters for minimum consumers and maximum messages and checks rabbitmq.
func (s *Sensu) Health(consumers int, messages int) ([]interface{}, error) {
	fmt.Printf("FIXME health args")
	return s.Get(fmt.Sprintf("health/%d/%d", consumers, messages))
}

// Info Will return the Sensu version along with rabbitmq and redis information.
func (s *Sensu) Info() ([]interface{}, error) {
	return s.Get("info")
}

// Delete Delete resource
func (s *Sensu) Delete(endpoint string) ([]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0
	fmt.Printf("FIXME Delete is NOT IMPLEMENTED")
	return s.GetList(endpoint, 0, 0)
}

// Get ...
func (s *Sensu) Get(endpoint string) ([]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0
	return s.GetList(endpoint, 0, 0)
}

// GetList Construct an API call and return the list of results
func (s *Sensu) GetList(endpoint string, limit int, offset int) ([]interface{}, error) {

	//ERROR GET LIST TODO deal with limit %d and offset %d", limit, offset

	url := fmt.Sprintf("%s/%s", s.URL, endpoint)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("Sensu.Get URL Parsing error: %q returned: %v", err, err)
	}

	client := http.Client{}
	res, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("HTTP request returned: %v", err)
	}

	body, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("Parsing response body returned: %v", err)
	}

	var results []interface{}
	if err := json.Unmarshal(body, &results); err != nil {
		return nil, fmt.Errorf("Parsing JSON-encoded response body: %v", err)
	}

	return results, nil
}

// Post to endpoint
func (s *Sensu) Post(endpoint string) ([]interface{}, error) {
	// Call a List with magic value of limit 0 and offset 0
	fmt.Printf("POST is not implemented yet\n")
	return s.Get(endpoint)
}
<|endoftext|>