<commit_before>// BTCChina exchange API

package btcchina

import (
	"bitfx/exchange"
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/gorilla/websocket"
)

// Client contains all exchange information
type Client struct {
	key, secret, symbol, currency, websocketURL, restURL, name string
	priority                                                   int
	position, fee, maxPos, availShort, availFunds              float64
	currencyCode                                               byte
}

// Exchange request format
type request struct {
	Event      string            `json:"event"`      // Event to request
	Channel    string            `json:"channel"`    // Channel on which to make request
	Parameters map[string]string `json:"parameters"` // Additional parameters
}

// New returns a pointer to a Client instance
func New(key, secret, symbol, currency string, priority int, fee, availShort, availFunds float64) *Client {
	return &Client{
		key:          key,
		secret:       secret,
		symbol:       symbol,
		currency:     currency,
		websocketURL: "websocket.btcchina.com/socket.io",
		restURL:      "api.btcchina.com/api_trade_v1.php",
		priority:     priority,
		fee:          fee,
		availShort:   availShort,
		availFunds:   availFunds,
		currencyCode: 1,
		name:         fmt.Sprintf("BTCChina(%s)", currency),
	}
}

// String implements the Stringer interface
func (client *Client) String() string {
	return client.name
}

// Priority returns the exchange priority for order execution
func (client *Client) Priority() int {
	return client.priority
}

// Fee returns the exchange order fee
func (client *Client) Fee() float64 {
	return client.fee
}

// SetPosition sets the exchange position
func (client *Client) SetPosition(pos float64) {
	client.position = pos
}

// Position returns the exchange position
func (client *Client) Position() float64 {
	return client.position
}

// Currency returns the exchange currency
func (client *Client) Currency() string {
	return client.currency
}

// CurrencyCode returns the exchange currency code
func (client *Client) CurrencyCode() byte {
	return client.currencyCode
}

// SetMaxPos sets the exchange max position
func (client *Client) SetMaxPos(maxPos float64) {
	client.maxPos = maxPos
}

// MaxPos returns the exchange max position
func (client *Client) MaxPos() float64 {
	return client.maxPos
}

// AvailFunds returns the exchange available funds
func (client *Client) AvailFunds() float64 {
	return client.availFunds
}

// AvailShort returns the exchange quantity available for short selling
func (client *Client) AvailShort() float64 {
	return client.availShort
}

// HasCrytpoFee returns true if fee is taken in cryptocurrency on buys
func (client *Client) HasCryptoFee() bool {
	return false
}

// CommunicateBook sends the latest available book data on the supplied channel
func (client *Client) CommunicateBook(bookChan chan<- exchange.Book, doneChan <-chan bool) exchange.Book {
	// Connect to Socket.IO
	ws, pingInterval, err := client.connectSocketIO()
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}

	// Get an initial book to return
	_, data, err := ws.ReadMessage()
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}
	book := client.convertToBook(data)

	// Run a read loop in new goroutine
	go client.runLoop(ws, pingInterval, bookChan, doneChan)

	return book
}

// Connect to Socket.IO
func (client *Client) connectSocketIO() (*websocket.Conn, time.Duration, error) {
	// Socket.IO handshake
	getURL := fmt.Sprintf("https://%s/?transport=polling", client.websocketURL)
	resp, err := http.Get(getURL)
	if err != nil {
		return nil, time.Duration(0), err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, time.Duration(0), err
	}
	resp.Body.Close()
	message := strings.TrimLeftFunc(string(body), func(char rune) bool { return string(char) != "{" })
	var session struct {
		Sid          string
		Upgrades     []string
		PingInterval int
		PingTimeout  int
	}
	if err := json.Unmarshal([]byte(message), &session); err != nil {
		return nil, time.Duration(0), err
	}
	for _, value := range session.Upgrades {
		if strings.ToLower(value) == "websocket" {
			break
		}
		return nil, time.Duration(0), fmt.Errorf("WebSocket upgrade not available")
	}
	wsURL := fmt.Sprintf("wss://%s/?transport=websocket&sid=%s", client.websocketURL, session.Sid)
	ws, _, err := websocket.DefaultDialer.Dial(wsURL, http.Header{})
	if err != nil {
		return nil, time.Duration(0), err
	}

	// Upgrade connection to WebSocket
	if err := ws.WriteMessage(1, []byte("52")); err != nil {
		return nil, time.Duration(0), err
	}
	_, data, err := ws.ReadMessage()
	if err != nil {
		return nil, time.Duration(0), err
	}
	if string(data) != "40" {
		return nil, time.Duration(0), fmt.Errorf("Failed WebSocket upgrade")
	}

	// Subscribe to channel
	subMsg := fmt.Sprintf("42[\"subscribe\",\"grouporder_%s%s\"]", client.currency, client.symbol)
	if err := ws.WriteMessage(1, []byte(subMsg)); err != nil {
		return nil, time.Duration(0), err
	}

	// Return WebSocket and ping interval
	return ws, time.Duration(session.PingInterval) * time.Millisecond, nil
}

// Websocket read loop
func (client *Client) runLoop(ws *websocket.Conn, pingInterval time.Duration, bookChan chan<- exchange.Book, doneChan <-chan bool) {
	// Syncronize access to *websocket.Conn
	receiveWS := make(chan *websocket.Conn)
	reconnectWS := make(chan bool)
	closeWS := make(chan bool)
	go func() {
	LOOP:
		for {
			select {
			// Request to use websocket
			case receiveWS <- ws:
			// Request to reconnect websocket
			case <-reconnectWS:
				ws.Close()
				var err error
				ws, _, err = client.connectSocketIO()
				// Keep trying on error
				for err != nil {
					log.Printf("%s WebSocket error: %s", client, err)
					time.Sleep(1 * time.Second)
					ws, _, err = client.connectSocketIO()
				}
			// Request to close websocket
			case <-closeWS:
				ws.Close()
				break LOOP
			}
		}
	}()

	// Read from websocket
	dataChan := make(chan []byte)
	go func() {
		for {
			(<-receiveWS).SetReadDeadline(time.Now().Add(pingInterval + time.Second))
			_, data, err := (<-receiveWS).ReadMessage()
			if err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			} else if string(data) != "3" {
				// If not a pong, send for processing
				dataChan <- data
			}
		}
	}()

	// Setup heartbeat
	ticker := time.NewTicker(pingInterval)
	ping := []byte("2")

	for {
		select {
		case <-doneChan:
			// End if notified
			ticker.Stop()
			closeWS <- true
			return
		case <-ticker.C:
			// Send Socket.IO ping
			if err := (<-receiveWS).WriteMessage(1, ping); err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			}
		case data := <-dataChan:
			// Process data and send out to user
			bookChan <- client.convertToBook(data)
		}
	}
}

// Convert websocket data to an exchange.Book
func (client *Client) convertToBook(data []byte) exchange.Book {
	// Remove Socket.IO crap
	message := strings.TrimLeftFunc(string(data), func(char rune) bool { return string(char) != "{" })
	message = strings.TrimRightFunc(message, func(char rune) bool { return string(char) != "}" })
	// Unmarshal
	var response struct {
		GroupOrder struct {
			Bid []struct {
				Price       float64
				TotalAmount float64
			}
			Ask []struct {
				Price       float64
				TotalAmount float64
			}
		}
	}
	if err := json.Unmarshal([]byte(message), &response); err != nil {
		return exchange.Book{Error: fmt.Errorf("%s book error: %s", client, err)}
	}

	// Translate into exchange.Book structure
	bids := make(exchange.BidItems, 5)
	asks := make(exchange.AskItems, 5)
	// Only depth of 5 is available
	for i := 0; i < 5; i++ {
		bids[i].Price = response.GroupOrder.Bid[i].Price
		bids[i].Amount = response.GroupOrder.Bid[i].TotalAmount
		asks[i].Price = response.GroupOrder.Ask[i].Price
		asks[i].Amount = response.GroupOrder.Ask[i].TotalAmount
	}
	sort.Sort(bids)
	sort.Sort(asks)

	// Return book
	return exchange.Book{
		Exg:   client,
		Time:  time.Now(),
		Bids:  bids,
		Asks:  asks,
		Error: nil,
	}
}

// SendOrder sends an order to the exchange
func (client *Client) SendOrder(action, otype string, amount, price float64) (int64, error) {
	// Set method
	var method string
	if action == "buy" {
		method = "buyOrder2"
	} else if action == "sell" {
		method = "sellOrder2"
	} else {
		return 0, fmt.Errorf("%s SendOrder error: only \"buy\" and \"sell\" actions supported", client)
	}

	// Check order type
	if otype != "limit" {
		return 0, fmt.Errorf("%s SendOrder error: only limit orders supported", client)
	}

	// Set params
	symbol := strings.ToUpper(client.symbol + client.currency)
	params := []string{strconv.FormatFloat(price, 'f', 2, 64), strconv.FormatFloat(amount, 'f', 4, 64), symbol}

	// Create request struct
	request := struct {
		Method string   `json:"method"`
		Params []string `json:"params"`
		ID     int      `json:"id"`
	}{
		method,
		params,
		1,
	}
	// Send POST
	data, err := client.post(method, strings.Join(params, ","), request)
	if err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}

	// Unmarshal
	var response struct {
		Result int64
		Error  struct {
			Code    int
			Message string
		}
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}
	spew.Dump(response)
	if response.Error.Message != "" {
		return 0, fmt.Errorf("%s SendOrder error code %d: %s", client, response.Error.Code, response.Error.Message)
	}

	return response.Result, nil
}

// CancelOrder cancels an order on the exchange
func (client *Client) CancelOrder(id int64) (bool, error) {
	return true, nil
}

// GetOrderStatus gets the status of an order on the exchange
func (client *Client) GetOrderStatus(id int64) (exchange.Order, error) {
	return exchange.Order{}, nil
}

// Authenticated POST
func (client *Client) post(method, params string, payload interface{}) ([]byte, error) {
	// Create signature to be signed
	tonce := strconv.FormatInt(time.Now().UnixNano()/1000, 10)
	signature := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s&params=%s",
		tonce, client.key, method, params)
	// Perform HMAC on signature using client.secret
	h := hmac.New(sha1.New, []byte(client.secret))
	h.Write([]byte(signature))

	// Marshal payload
	body, err := json.Marshal(payload)
	if err != nil {
		return []byte{}, err
	}
	// Create http request using specified url
	url := fmt.Sprintf("https://%s:%x@%s", client.key, h.Sum(nil), client.restURL)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return []byte{}, err
	}

	// Add tonce header
	req.Header.Add("Json-Rpc-Tonce", tonce)

	// Send POST
	httpClient := http.Client{}
	resp, err := httpClient.Do(req)
	if err != nil {
		return []byte{}, err
	}
	if resp.StatusCode != 200 {
		return []byte{}, fmt.Errorf(resp.Status)
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)
}
<commit_msg>dry out order methods<commit_after>// BTCChina exchange API

package btcchina

import (
	"bitfx/exchange"
	"bytes"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/websocket"
)

// Client contains all exchange information
type Client struct {
	key, secret, symbol, currency, websocketURL, restURL, name, market string
	priority                                                           int
	position, fee, maxPos, availShort, availFunds                      float64
	currencyCode                                                       byte
}

// Exchange request format
type request struct {
	Method string        `json:"method"`
	Params []interface{} `json:"params"`
	ID     int           `json:"id"`
}

// New returns a pointer to a Client instance
func New(key, secret, symbol, currency string, priority int, fee, availShort, availFunds float64) *Client {
	return &Client{
		key:          key,
		secret:       secret,
		symbol:       symbol,
		currency:     currency,
		websocketURL: "websocket.btcchina.com/socket.io",
		restURL:      "api.btcchina.com/api_trade_v1.php",
		priority:     priority,
		fee:          fee,
		availShort:   availShort,
		availFunds:   availFunds,
		currencyCode: 1,
		name:         fmt.Sprintf("BTCChina(%s)", currency),
		market:       strings.ToUpper(symbol + currency),
	}
}

// String implements the Stringer interface
func (client *Client) String() string {
	return client.name
}

// Priority returns the exchange priority for order execution
func (client *Client) Priority() int {
	return client.priority
}

// Fee returns the exchange order fee
func (client *Client) Fee() float64 {
	return client.fee
}

// SetPosition sets the exchange position
func (client *Client) SetPosition(pos float64) {
	client.position = pos
}

// Position returns the exchange position
func (client *Client) Position() float64 {
	return client.position
}

// Currency returns the exchange currency
func (client *Client) Currency() string {
	return client.currency
}

// CurrencyCode returns the exchange currency code
func (client *Client) CurrencyCode() byte {
	return client.currencyCode
}

// SetMaxPos sets the exchange max position
func (client *Client) SetMaxPos(maxPos float64) {
	client.maxPos = maxPos
}

// MaxPos returns the exchange max position
func (client *Client) MaxPos() float64 {
	return client.maxPos
}

// AvailFunds returns the exchange available funds
func (client *Client) AvailFunds() float64 {
	return client.availFunds
}

// AvailShort returns the exchange quantity available for short selling
func (client *Client) AvailShort() float64 {
	return client.availShort
}

// HasCrytpoFee returns true if fee is taken in cryptocurrency on buys
func (client *Client) HasCryptoFee() bool {
	return false
}

// CommunicateBook sends the latest available book data on the supplied channel
func (client *Client) CommunicateBook(bookChan chan<- exchange.Book, doneChan <-chan bool) exchange.Book {
	// Connect to Socket.IO
	ws, pingInterval, err := client.connectSocketIO()
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}

	// Get an initial book to return
	_, data, err := ws.ReadMessage()
	if err != nil {
		return exchange.Book{Error: fmt.Errorf("%s CommunicateBook error: %s", client, err)}
	}
	book := client.convertToBook(data)

	// Run a read loop in new goroutine
	go client.runLoop(ws, pingInterval, bookChan, doneChan)

	return book
}

// Connect to Socket.IO
func (client *Client) connectSocketIO() (*websocket.Conn, time.Duration, error) {
	// Socket.IO handshake
	getURL := fmt.Sprintf("https://%s/?transport=polling", client.websocketURL)
	resp, err := http.Get(getURL)
	if err != nil {
		return nil, time.Duration(0), err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, time.Duration(0), err
	}
	resp.Body.Close()
	message := strings.TrimLeftFunc(string(body), func(char rune) bool { return string(char) != "{" })
	var session struct {
		Sid          string
		Upgrades     []string
		PingInterval int
		PingTimeout  int
	}
	if err := json.Unmarshal([]byte(message), &session); err != nil {
		return nil, time.Duration(0), err
	}
	for _, value := range session.Upgrades {
		if strings.ToLower(value) == "websocket" {
			break
		}
		return nil, time.Duration(0), fmt.Errorf("WebSocket upgrade not available")
	}
	wsURL := fmt.Sprintf("wss://%s/?transport=websocket&sid=%s", client.websocketURL, session.Sid)
	ws, _, err := websocket.DefaultDialer.Dial(wsURL, http.Header{})
	if err != nil {
		return nil, time.Duration(0), err
	}

	// Upgrade connection to WebSocket
	if err := ws.WriteMessage(1, []byte("52")); err != nil {
		return nil, time.Duration(0), err
	}
	_, data, err := ws.ReadMessage()
	if err != nil {
		return nil, time.Duration(0), err
	}
	if string(data) != "40" {
		return nil, time.Duration(0), fmt.Errorf("Failed WebSocket upgrade")
	}

	// Subscribe to channel
	subMsg := fmt.Sprintf("42[\"subscribe\",\"grouporder_%s%s\"]", client.currency, client.symbol)
	if err := ws.WriteMessage(1, []byte(subMsg)); err != nil {
		return nil, time.Duration(0), err
	}

	// Return WebSocket and ping interval
	return ws, time.Duration(session.PingInterval) * time.Millisecond, nil
}

// Websocket read loop
func (client *Client) runLoop(ws *websocket.Conn, pingInterval time.Duration, bookChan chan<- exchange.Book, doneChan <-chan bool) {
	// Syncronize access to *websocket.Conn
	receiveWS := make(chan *websocket.Conn)
	reconnectWS := make(chan bool)
	closeWS := make(chan bool)
	go func() {
	LOOP:
		for {
			select {
			// Request to use websocket
			case receiveWS <- ws:
			// Request to reconnect websocket
			case <-reconnectWS:
				ws.Close()
				var err error
				ws, _, err = client.connectSocketIO()
				// Keep trying on error
				for err != nil {
					log.Printf("%s WebSocket error: %s", client, err)
					time.Sleep(1 * time.Second)
					ws, _, err = client.connectSocketIO()
				}
			// Request to close websocket
			case <-closeWS:
				ws.Close()
				break LOOP
			}
		}
	}()

	// Read from websocket
	dataChan := make(chan []byte)
	go func() {
		for {
			(<-receiveWS).SetReadDeadline(time.Now().Add(pingInterval + time.Second))
			_, data, err := (<-receiveWS).ReadMessage()
			if err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			} else if string(data) != "3" {
				// If not a pong, send for processing
				dataChan <- data
			}
		}
	}()

	// Setup heartbeat
	ticker := time.NewTicker(pingInterval)
	ping := []byte("2")

	for {
		select {
		case <-doneChan:
			// End if notified
			ticker.Stop()
			closeWS <- true
			return
		case <-ticker.C:
			// Send Socket.IO ping
			if err := (<-receiveWS).WriteMessage(1, ping); err != nil {
				// Reconnect on error
				log.Printf("%s WebSocket error: %s", client, err)
				reconnectWS <- true
			}
		case data := <-dataChan:
			// Process data and send out to user
			bookChan <- client.convertToBook(data)
		}
	}
}

// Convert websocket data to an exchange.Book
func (client *Client) convertToBook(data []byte) exchange.Book {
	// Remove Socket.IO crap
	message := strings.TrimLeftFunc(string(data), func(char rune) bool { return string(char) != "{" })
	message = strings.TrimRightFunc(message, func(char rune) bool { return string(char) != "}" })
	// Unmarshal
	var response struct {
		GroupOrder struct {
			Bid []struct {
				Price       float64
				TotalAmount float64
			}
			Ask []struct {
				Price       float64
				TotalAmount float64
			}
		}
	}
	if err := json.Unmarshal([]byte(message), &response); err != nil {
		return exchange.Book{Error: fmt.Errorf("%s book error: %s", client, err)}
	}

	// Translate into exchange.Book structure
	bids := make(exchange.BidItems, 5)
	asks := make(exchange.AskItems, 5)
	// Only depth of 5 is available
	for i := 0; i < 5; i++ {
		bids[i].Price = response.GroupOrder.Bid[i].Price
		bids[i].Amount = response.GroupOrder.Bid[i].TotalAmount
		asks[i].Price = response.GroupOrder.Ask[i].Price
		asks[i].Amount = response.GroupOrder.Ask[i].TotalAmount
	}
	sort.Sort(bids)
	sort.Sort(asks)

	// Return book
	return exchange.Book{
		Exg:   client,
		Time:  time.Now(),
		Bids:  bids,
		Asks:  asks,
		Error: nil,
	}
}

// SendOrder sends an order to the exchange
func (client *Client) SendOrder(action, otype string, amount, price float64) (int64, error) {
	// Set method
	var method string
	if action == "buy" {
		method = "buyOrder2"
	} else if action == "sell" {
		method = "sellOrder2"
	} else {
		return 0, fmt.Errorf("%s SendOrder error: only \"buy\" and \"sell\" actions supported", client)
	}

	// Check order type
	if otype != "limit" {
		return 0, fmt.Errorf("%s SendOrder error: only limit orders supported", client)
	}

	// Set params
	strPrice := strconv.FormatFloat(price, 'f', 2, 64)
	strAmount := strconv.FormatFloat(amount, 'f', 4, 64)
	params := []interface{}{strPrice, strAmount, client.market}
	paramString := strings.Join([]string{strPrice, strAmount, client.market}, ",")

	// Send POST
	req := request{method, params, 1}
	data, err := client.post(method, paramString, req)
	if err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}

	// Unmarshal
	var response struct {
		Result int64
		Error  struct {
			Code    int
			Message string
		}
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return 0, fmt.Errorf("%s SendOrder error: %s", client, err)
	}
	if response.Error.Message != "" {
		return 0, fmt.Errorf("%s SendOrder error code %d: %s", client, response.Error.Code, response.Error.Message)
	}

	return response.Result, nil
}

// CancelOrder cancels an order on the exchange
func (client *Client) CancelOrder(id int64) (bool, error) {
	// Set params
	method := "cancelOrder"
	params := []interface{}{id, client.market}
	paramString := strconv.FormatInt(id, 10) + "," + client.market

	// Send POST
	req := request{method, params, 1}
	data, err := client.post(method, paramString, req)
	if err != nil {
		return false, fmt.Errorf("%s CancelOrder error: %s", client, err)
	}

	// Unmarshal
	var response struct {
		Result bool
		Error  struct {
			Code    int
			Message string
		}
	}
	if err := json.Unmarshal(data, &response); err != nil {
		return false, fmt.Errorf("%s CancelOrder error: %s", client, err)
	}
	if response.Error.Message != "" {
		return false, fmt.Errorf("%s CancelOrder error code %d: %s", client, response.Error.Code, response.Error.Message)
	}

	return response.Result, nil
}

// GetOrderStatus gets the status of an order on the exchange
func (client *Client) GetOrderStatus(id int64) (exchange.Order, error) {
	// method := "getOrder"
	// params := []interface{}{id, client.market}
	return exchange.Order{}, nil
}

// Authenticated POST
func (client *Client) post(method, params string, payload interface{}) ([]byte, error) {
	// Create signature to be signed
	tonce := strconv.FormatInt(time.Now().UnixNano()/1000, 10)
	signature := fmt.Sprintf("tonce=%s&accesskey=%s&requestmethod=post&id=1&method=%s&params=%s",
		tonce, client.key, method, params)
	// Perform HMAC on signature using client.secret
	h := hmac.New(sha1.New, []byte(client.secret))
	h.Write([]byte(signature))

	// Marshal payload
	body, err := json.Marshal(payload)
	if err != nil {
		return []byte{}, err
	}
	// Create http request using specified url
	url := fmt.Sprintf("https://%s:%x@%s", client.key, h.Sum(nil), client.restURL)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
	if err != nil {
		return []byte{}, err
	}

	// Add tonce header
	req.Header.Add("Json-Rpc-Tonce", tonce)

	// Send POST
	httpClient := http.Client{}
	resp, err := httpClient.Do(req)
	if err != nil {
		return []byte{}, err
	}
	if resp.StatusCode != 200 {
		return []byte{}, fmt.Errorf(resp.Status)
	}
	defer resp.Body.Close()

	return ioutil.ReadAll(resp.Body)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"time"
)

const VERSION string = "1.0.8"
const SUCCESS_EXIT int = 0
const ERROR_EXIT int = 1
const LIMIT_WAIT_COUNT int = 1260 // 20sec * 1260 = 7 hours
const API_SERVER string = "https://api.vaddy.net"

type CrawlSearch struct {
	Total int               `json:"total"`
	Items []CrawlSearchItem `json:"items"`
}

type CrawlSearchItem struct {
	CrawlId int `json:"id"`
}

type StartScan struct {
	ScanID string `json:"scan_id"`
}

type ScanResult struct {
	Status        string `json:"status"`
	AlertCount    int    `json:"alert_count"`
	ScanCount     int    `json:"scan_count"`
	ScanResultUrl string `json:"scan_result_url"`
	Complete      int    `json:"complete"`
	CrawlId       int    `json:"crawl_id"`
	CrawlLabel    string `json:"crawl_label"`
}

func (s ScanResult) IsIncomplete() bool {
	return s.AlertCount == 0 && s.Complete < 100
}

func init() {
	var showVersion bool
	flag.BoolVar(&showVersion, "version", false, "Show version")
	flag.BoolVar(&showVersion, "v", false, "Show version")
	flag.Parse()

	if showVersion {
		fmt.Printf("%s", VERSION)
		os.Exit(SUCCESS_EXIT)
	}
}

func main() {
	fmt.Println("==== Start VAddy Scan (Version " + VERSION + ")====")

	var auth_key, user, fqdn, crawl, verification_code, project_id string = getApiParamsFromArgsOrEnv()

	if checkNeedToGetCrawlId(crawl) {
		fmt.Println("Start to get crawl ID from keyword: " + crawl)
		crawl = getCrawlId(auth_key, user, fqdn, crawl, verification_code, project_id)
	}

	scan_id := startScan(auth_key, user, fqdn, crawl, verification_code, project_id)

	var wait_count int = 0
	var sleep_sec int = 20

	for {
		checkScanResult(auth_key, user, fqdn, scan_id, wait_count, verification_code, project_id)

		sleep_sec = 20
		if wait_count < 10 {
			sleep_sec = 3
		}
		time.Sleep(time.Duration(sleep_sec) * time.Second)

		wait_count++
		if wait_count > LIMIT_WAIT_COUNT {
			fmt.Println("Error: time out")
			os.Exit(ERROR_EXIT)
		}
	}
}

func getApiParamsFromArgsOrEnv() (string, string, string, string, string, string) {
	var auth_key, user, fqdn, crawl, verification_code string
	verification_code, _ = os.LookupEnv("VADDY_VERIFICATION_CODE")

	if len(os.Args) < 4 {
		return getArgsFromEnv(verification_code)
	}

	auth_key = os.Args[1]
	user = os.Args[2]
	fqdn = os.Args[3]
	if len(os.Args) >= 5 {
		crawl = os.Args[4]
	}
	return auth_key, user, fqdn, crawl, verification_code, ""
}

func getArgsFromEnv(verification_code string) (string, string, string, string, string, string) {
	var auth_key, user, fqdn, crawl string
	auth_key, ok1 := os.LookupEnv("VADDY_TOKEN")
	user, ok2 := os.LookupEnv("VADDY_USER")
	fqdn, ok3 := os.LookupEnv("VADDY_HOST")
	crawl, _ = os.LookupEnv("VADDY_CRAWL")
	project_id, ok4 := os.LookupEnv("VADDY_PROJECT_ID")

	// v1
	if ok1 && ok2 && ok3 {
		return auth_key, user, fqdn, crawl, verification_code, ""
	}
	// v2
	if ok1 && ok2 && ok4 {
		return auth_key, user, "", crawl, verification_code, project_id
	}

	fmt.Println("Missing arguments or system env.")
	fmt.Println("USAGE: vaddy.go ApiKey UserId FQDN CrawlID/Label(optional)")
	os.Exit(ERROR_EXIT)

	return "", "", "", "", "", ""
}

func startScan(auth_key string, user string, fqdn string, crawl string, verification_code, project_id string) string {
	values := url.Values{}
	values.Add("auth_key", auth_key)
	values.Add("user", user)
	values.Add("fqdn", fqdn)
	values.Add("action", "start")
	values.Add("verification_code", verification_code)
	if len(crawl) > 0 {
		values.Add("crawl_id", crawl)
	}
	if len(project_id) > 0 {
		values.Add("project_id", project_id)
	}

	api_server := getApiServerName()
	api_version := detectApiVersion(project_id)
	res, err := http.PostForm(api_server+"/"+api_version+"/scan", values)
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
	defer res.Body.Close()
	json_response := getResponseData(res)
	scanId := getScanId(json_response)
	//fmt.Println("scanId: " + scanId)
	return scanId
}

func getScanResult(auth_key string, user string, fqdn string, scan_id string, verification_code, project_id string) []byte {
	values := url.Values{}
	values.Add("auth_key", auth_key)
	values.Add("user", user)
	values.Add("fqdn", fqdn)
	values.Add("scan_id", scan_id)
	values.Add("verification_code", verification_code)
	if len(project_id) > 0 {
		values.Add("project_id", project_id)
	}

	api_server := getApiServerName()
	api_version := detectApiVersion(project_id)
	res, err := http.Get(api_server + "/" + api_version + "/scan/result?" + values.Encode())
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
	defer res.Body.Close()

	json_response := getResponseData(res)
	return json_response
}

func checkScanResult(auth_key string, user string, fqdn string, scan_id string, count int, verification_code, project_id string) {
	json_response := getScanResult(auth_key, user, fqdn, scan_id, verification_code, project_id)

	var scan_result ScanResult
	convertJsonToStruct(json_response, &scan_result)

	status := scan_result.Status
	switch status {
	case "scanning":
		if count > 0 && (count%60 == 0) { //wrap every 60 dots.
			fmt.Println(".")
		} else {
			fmt.Print(".")
		}
	case "canceled":
		fmt.Println(scan_result.Status)
		os.Exit(ERROR_EXIT)
	case "finish":
		//fmt.Println(string(json_response) + "\n")
		fmt.Println(".")
		fmt.Println("Server: " + fqdn)
		fmt.Println("scanId: " + scan_id)
		fmt.Println("Result URL: " + scan_result.ScanResultUrl)
		fmt.Println("Crawl ID: " + strconv.Itoa(scan_result.CrawlId))
		fmt.Println("Crawl Label: " + scan_result.CrawlLabel)

		if scan_result.AlertCount > 0 {
			fmt.Print("Vulnerabilities: ")
			fmt.Println(scan_result.AlertCount)
			fmt.Println("Warning!!!")
			postSlackVulnerabilitiesWarning(scan_result.AlertCount, fqdn, scan_id, scan_result.ScanResultUrl)
			os.Exit(ERROR_EXIT)
		} else if scan_result.IsIncomplete() {
			fmt.Printf("Notice: Scan was NOT complete (%d%%).\n", scan_result.Complete)
			fmt.Println("No vulnerabilities.")
			postSlackIncompleteNotice(fqdn, scan_id, scan_result)
			os.Exit(SUCCESS_EXIT)
		} else if scan_result.ScanCount == 0 {
			fmt.Println("ERROR: VAddy was not able to scan your sever. Check the result on the Result URL.")
			os.Exit(ERROR_EXIT)
		} else {
			fmt.Println("Scan Success. No vulnerabilities!")
			os.Exit(SUCCESS_EXIT)
		}
	}
}

func getCrawlId(auth_key string, user string, fqdn string, search_label string, verification_code, project_id string) string {
	json_response := doCrawlSearch(auth_key, user, fqdn, search_label, verification_code, project_id)
	//fmt.Println(string(json_response))

	var crawl_result CrawlSearch
	convertJsonToStruct(json_response, &crawl_result)
	if crawl_result.Total == 0 {
		fmt.Println("can not find crawl id. using latest crawl id.")
		return ""
	}
	var crawl_id int = crawl_result.Items[0].CrawlId
	fmt.Printf("Found %d results. Using CrawlID: %d \n\n", crawl_result.Total, crawl_id)
	return strconv.Itoa(crawl_id)
}

func doCrawlSearch(auth_key string, user string, fqdn string, search_label string, verification_code, project_id string) []byte {
	values := url.Values{}
	values.Add("auth_key", auth_key)
	values.Add("user", user)
	values.Add("fqdn", fqdn)
	values.Add("search_label", search_label)
	values.Add("verification_code", verification_code)
	if len(project_id) > 0 {
		values.Add("project_id", project_id)
	}

	api_server := getApiServerName()
	api_version := detectApiVersion(project_id)
	res, err := http.Get(api_server + "/" + api_version + "/crawl?" + values.Encode())
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
	defer res.Body.Close()

	json_response := getResponseData(res)
	return json_response
}

func getResponseData(resp *http.Response) []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(string(body))
		os.Exit(ERROR_EXIT)
	}
	status_code := resp.StatusCode
	//fmt.Println(status_code)
	if status_code != 200 {
		fmt.Println("Network/Auth error\n" + string(body))
		os.Exit(ERROR_EXIT)
	}
	return []byte(body)
}

func getScanId(jsonByteData []byte) string {
	var scan_result StartScan
	convertJsonToStruct(jsonByteData, &scan_result)
	return scan_result.ScanID
}

func convertJsonToStruct(jsonByteData []byte, structData interface{}) {
	err := json.Unmarshal(jsonByteData, structData)
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
}

func getApiServerName() string {
	api_server, ok := os.LookupEnv("VADDY_API_SERVER")
	if ok {
		//if api server does not contain protocol name, add https://
		var regex string = `^https://.*$`
		if regexp.MustCompile(regex).Match([]byte(api_server)) {
			return api_server
		}
		return "https://" + api_server
	}
	return API_SERVER
}

func checkNeedToGetCrawlId(str string) bool {
	if len(str) == 0 || str == "" {
		return false
	}
	var regex string = `[^0-9]`
	return regexp.MustCompile(regex).Match([]byte(str))
}

func postSlackVulnerabilitiesWarning(alertCount int, fqdn string, scanID string, scanResultURL string) {
	title := fmt.Sprintf("VAddy Scan found Vulnerabilities: %d Warning!!!\n", alertCount)
	text := "Server: " + fqdn + "\n"
	text += "Scan ID: " + scanID + "\n"
	text += "Result URL: " + scanResultURL

	postSlack(title, text)
}

func postSlackIncompleteNotice(fqdn string, scanID string, scanResult ScanResult) {
	title := fmt.Sprintf("Notice: VAddy Scan was NOT complete (%d%%).\n", scanResult.Complete)
	text := "Server: " + fqdn + "\n"
	text += "Scan ID: " + scanID + "\n"
	text += "Result URL: " + scanResult.ScanResultUrl

	postSlack(title, text)
}

func postSlack(title, text string) {
	slackWebhookURL, ok1 := os.LookupEnv("SLACK_WEBHOOK_URL")

	if ok1 {
		slackUsername, ok2 := os.LookupEnv("SLACK_USERNAME")
		if !ok2 {
			slackUsername = ""
		}
		slackChannel, ok3 := os.LookupEnv("SLACK_CHANNEL")
		if !ok3 {
			slackChannel = ""
		}
		iconEmoji, ok4 := os.LookupEnv("SLACK_ICON_EMOJI")
		if !ok4 {
			iconEmoji = ""
		}
		iconURL, ok5 := os.LookupEnv("SLACK_ICON_URL")
		if !ok5 {
			iconURL = ""
		}

		type attachments struct {
			Color string `json:"color"`
			Title string `json:"title"`
			Text  string `json:"text"`
		}

		type slack struct {
			Username     string        `json:"username"`
			IconEmoji    string        `json:"icon_emoji"`
			IconURL      string        `json:"icon_url"`
			Channel      string        `json:"channel"`
			Text         string        `json:"text"`
			Attachements []attachments `json:"attachments"`
		}

		webhooks := slack{
			Username:  slackUsername,
			IconEmoji: iconEmoji,
			IconURL:   iconURL,
			Channel:   slackChannel,
			Text:      "VAddy Scan (Version " + VERSION + ")",
			Attachements: []attachments{
				{
					Color: "warning",
					Title: title,
					Text:  text,
				},
			},
		}

		params, _ := json.Marshal(webhooks)
		resp, err := http.PostForm(
			slackWebhookURL,
			url.Values{"payload": {string(params)}},
		)
		if err == nil {
			defer resp.Body.Close()
		}
	}
}

func detectApiVersion(project_id string) string {
	if len(project_id) > 0 {
		return "v2"
	}

	return "v1"
}
<commit_msg>Print OS env value when there are env errors<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"time"
)

const VERSION string = "1.0.8"
const SUCCESS_EXIT int = 0
const ERROR_EXIT int = 1
const LIMIT_WAIT_COUNT int = 1260 // 20sec * 1260 = 7 hours
const API_SERVER string = "https://api.vaddy.net"

type CrawlSearch struct {
	Total int               `json:"total"`
	Items []CrawlSearchItem `json:"items"`
}

type CrawlSearchItem struct {
	CrawlId int `json:"id"`
}

type StartScan struct {
	ScanID string `json:"scan_id"`
}

type ScanResult struct {
	Status        string `json:"status"`
	AlertCount    int    `json:"alert_count"`
	ScanCount     int    `json:"scan_count"`
	ScanResultUrl string `json:"scan_result_url"`
	Complete      int    `json:"complete"`
	CrawlId       int    `json:"crawl_id"`
	CrawlLabel    string `json:"crawl_label"`
}

func (s ScanResult) IsIncomplete() bool {
	return s.AlertCount == 0 && s.Complete < 100
}

func init() {
	var showVersion bool
	flag.BoolVar(&showVersion, "version", false, "Show version")
	flag.BoolVar(&showVersion, "v", false, "Show version")
	flag.Parse()

	if showVersion {
		fmt.Printf("%s", VERSION)
		os.Exit(SUCCESS_EXIT)
	}
}

func main() {
	fmt.Println("==== Start VAddy Scan (Version " + VERSION + ")====")

	var auth_key, user, fqdn, crawl, verification_code, project_id string = getApiParamsFromArgsOrEnv()

	if checkNeedToGetCrawlId(crawl) {
		fmt.Println("Start to get crawl ID from keyword: " + crawl)
		crawl = getCrawlId(auth_key, user, fqdn, crawl, verification_code, project_id)
	}

	scan_id := startScan(auth_key, user, fqdn, crawl, verification_code, project_id)

	var wait_count int = 0
	var sleep_sec int = 20

	for {
		checkScanResult(auth_key, user, fqdn, scan_id, wait_count, verification_code, project_id)

		sleep_sec = 20
		if wait_count < 10 {
			sleep_sec = 3
		}
		time.Sleep(time.Duration(sleep_sec) * time.Second)

		wait_count++
		if wait_count > LIMIT_WAIT_COUNT {
			fmt.Println("Error: time out")
			os.Exit(ERROR_EXIT)
		}
	}
}

func getApiParamsFromArgsOrEnv() (string, string, string, string, string, string) {
	var auth_key, user, fqdn, crawl, verification_code string
	verification_code, _ = os.LookupEnv("VADDY_VERIFICATION_CODE")

	if len(os.Args) < 4 {
		return getArgsFromEnv(verification_code)
	}

	auth_key = os.Args[1]
	user = os.Args[2]
	fqdn = os.Args[3]
	if len(os.Args) >= 5 {
		crawl = os.Args[4]
	}
	return auth_key, user, fqdn, crawl, verification_code, ""
}

func getArgsFromEnv(verification_code string) (string, string, string, string, string, string) {
	var auth_key, user, fqdn, crawl string
	auth_key, ok1 := os.LookupEnv("VADDY_TOKEN")
	user, ok2 := os.LookupEnv("VADDY_USER")
	fqdn, ok3 := os.LookupEnv("VADDY_HOST")
	crawl, _ = os.LookupEnv("VADDY_CRAWL")
	project_id, ok4 := os.LookupEnv("VADDY_PROJECT_ID")

	// v1
	if ok1 && ok2 && ok3 {
		return auth_key, user, fqdn, crawl, verification_code, ""
	}
	// v2
	if ok1 && ok2 && ok4 {
		return auth_key, user, "", crawl, verification_code, project_id
	}

	fmt.Println("Missing arguments or system env.")
	fmt.Println(" ENV VADDY_USER: " + user)
	fmt.Println(" ENV VADDY_PROJECT_ID(V2): " + project_id)
	fmt.Println(" ENV VADDY_HOST(V1): " + fqdn)
	os.Exit(ERROR_EXIT)

	return "", "", "", "", "", ""
}

func startScan(auth_key string, user string, fqdn string, crawl string, verification_code, project_id string) string {
	values := url.Values{}
	values.Add("auth_key", auth_key)
	values.Add("user", user)
	values.Add("fqdn", fqdn)
	values.Add("action", "start")
	values.Add("verification_code", verification_code)
	if len(crawl) > 0 {
		values.Add("crawl_id", crawl)
	}
	if len(project_id) > 0 {
		values.Add("project_id", project_id)
	}

	api_server := getApiServerName()
	api_version := detectApiVersion(project_id)
	res, err := http.PostForm(api_server+"/"+api_version+"/scan", values)
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
	defer res.Body.Close()
	json_response := getResponseData(res)
	scanId := getScanId(json_response)
	//fmt.Println("scanId: " + scanId)
	return scanId
}

func getScanResult(auth_key string, user string, fqdn string, scan_id string, verification_code, project_id string) []byte {
	values := url.Values{}
	values.Add("auth_key", auth_key)
	values.Add("user", user)
	values.Add("fqdn", fqdn)
	values.Add("scan_id", scan_id)
	values.Add("verification_code", verification_code)
	if len(project_id) > 0 {
		values.Add("project_id", project_id)
	}

	api_server := getApiServerName()
	api_version := detectApiVersion(project_id)
	res, err := http.Get(api_server + "/" + api_version + "/scan/result?" + values.Encode())
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
	defer res.Body.Close()

	json_response := getResponseData(res)
	return json_response
}

func checkScanResult(auth_key string, user string, fqdn string, scan_id string, count int, verification_code, project_id string) {
	json_response := getScanResult(auth_key, user, fqdn, scan_id, verification_code, project_id)

	var scan_result ScanResult
	convertJsonToStruct(json_response, &scan_result)

	status := scan_result.Status
	switch status {
	case "scanning":
		if count > 0 && (count%60 == 0) { //wrap every 60 dots.
			fmt.Println(".")
		} else {
			fmt.Print(".")
		}
	case "canceled":
		fmt.Println(scan_result.Status)
		os.Exit(ERROR_EXIT)
	case "finish":
		//fmt.Println(string(json_response) + "\n")
		fmt.Println(".")
		fmt.Println("Server: " + fqdn)
		fmt.Println("scanId: " + scan_id)
		fmt.Println("Result URL: " + scan_result.ScanResultUrl)
		fmt.Println("Crawl ID: " + strconv.Itoa(scan_result.CrawlId))
		fmt.Println("Crawl Label: " + scan_result.CrawlLabel)

		if scan_result.AlertCount > 0 {
			fmt.Print("Vulnerabilities: ")
			fmt.Println(scan_result.AlertCount)
			fmt.Println("Warning!!!")
			postSlackVulnerabilitiesWarning(scan_result.AlertCount, fqdn, scan_id, scan_result.ScanResultUrl)
			os.Exit(ERROR_EXIT)
		} else if scan_result.IsIncomplete() {
			fmt.Printf("Notice: Scan was NOT complete (%d%%).\n", scan_result.Complete)
			fmt.Println("No vulnerabilities.")
			postSlackIncompleteNotice(fqdn, scan_id, scan_result)
			os.Exit(SUCCESS_EXIT)
		} else if scan_result.ScanCount == 0 {
			fmt.Println("ERROR: VAddy was not able to scan your sever. Check the result on the Result URL.")
			os.Exit(ERROR_EXIT)
		} else {
			fmt.Println("Scan Success. No vulnerabilities!")
			os.Exit(SUCCESS_EXIT)
		}
	}
}

func getCrawlId(auth_key string, user string, fqdn string, search_label string, verification_code, project_id string) string {
	json_response := doCrawlSearch(auth_key, user, fqdn, search_label, verification_code, project_id)
	//fmt.Println(string(json_response))

	var crawl_result CrawlSearch
	convertJsonToStruct(json_response, &crawl_result)
	if crawl_result.Total == 0 {
		fmt.Println("can not find crawl id. using latest crawl id.")
		return ""
	}
	var crawl_id int = crawl_result.Items[0].CrawlId
	fmt.Printf("Found %d results. Using CrawlID: %d \n\n", crawl_result.Total, crawl_id)
	return strconv.Itoa(crawl_id)
}

func doCrawlSearch(auth_key string, user string, fqdn string, search_label string, verification_code, project_id string) []byte {
	values := url.Values{}
	values.Add("auth_key", auth_key)
	values.Add("user", user)
	values.Add("fqdn", fqdn)
	values.Add("search_label", search_label)
	values.Add("verification_code", verification_code)
	if len(project_id) > 0 {
		values.Add("project_id", project_id)
	}

	api_server := getApiServerName()
	api_version := detectApiVersion(project_id)
	res, err := http.Get(api_server + "/" + api_version + "/crawl?" + values.Encode())
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
	defer res.Body.Close()

	json_response := getResponseData(res)
	return json_response
}

func getResponseData(resp *http.Response) []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(string(body))
		os.Exit(ERROR_EXIT)
	}
	status_code := resp.StatusCode
	//fmt.Println(status_code)
	if status_code != 200 {
		fmt.Println("Network/Auth error\n" + string(body))
		os.Exit(ERROR_EXIT)
	}
	return []byte(body)
}

func getScanId(jsonByteData []byte) string {
	var scan_result StartScan
	convertJsonToStruct(jsonByteData, &scan_result)
	return scan_result.ScanID
}

func convertJsonToStruct(jsonByteData []byte, structData interface{}) {
	err := json.Unmarshal(jsonByteData, structData)
	if err != nil {
		fmt.Println(err)
		os.Exit(ERROR_EXIT)
	}
}

func getApiServerName() string {
	api_server, ok := os.LookupEnv("VADDY_API_SERVER")
	if ok {
		//if api server does not contain protocol name, add https://
		var regex string = `^https://.*$`
		if regexp.MustCompile(regex).Match([]byte(api_server)) {
			return api_server
		}
		return "https://" + api_server
	}
	return API_SERVER
}

func checkNeedToGetCrawlId(str string) bool {
	if len(str) == 0 || str == "" {
		return false
	}
	var regex string = `[^0-9]`
	return regexp.MustCompile(regex).Match([]byte(str))
}

func postSlackVulnerabilitiesWarning(alertCount int, fqdn string, scanID string, scanResultURL string) {
	title := fmt.Sprintf("VAddy Scan found Vulnerabilities: %d Warning!!!\n", alertCount)
	text := "Server: " + fqdn + "\n"
	text += "Scan ID: " + scanID + "\n"
	text += "Result URL: " + scanResultURL

	postSlack(title, text)
}

func postSlackIncompleteNotice(fqdn string, scanID string, scanResult ScanResult) {
	title := fmt.Sprintf("Notice: VAddy Scan was NOT complete (%d%%).\n", scanResult.Complete)
	text := "Server: " + fqdn + "\n"
	text += "Scan ID: " + scanID + "\n"
	text += "Result URL: " + scanResult.ScanResultUrl

	postSlack(title, text)
}

func postSlack(title, text string) {
	slackWebhookURL, ok1 := os.LookupEnv("SLACK_WEBHOOK_URL")

	if ok1 {
		slackUsername, ok2 := os.LookupEnv("SLACK_USERNAME")
		if !ok2 {
			slackUsername = ""
		}
		slackChannel, ok3 := os.LookupEnv("SLACK_CHANNEL")
		if !ok3 {
			slackChannel = ""
		}
		iconEmoji, ok4 := os.LookupEnv("SLACK_ICON_EMOJI")
		if !ok4 {
			iconEmoji = ""
		}
		iconURL, ok5 := os.LookupEnv("SLACK_ICON_URL")
		if !ok5 {
			iconURL = ""
		}

		type attachments struct {
			Color string `json:"color"`
			Title string `json:"title"`
			Text  string `json:"text"`
		}

		type slack struct {
			Username     string        `json:"username"`
			IconEmoji    string        `json:"icon_emoji"`
			IconURL      string        `json:"icon_url"`
			Channel      string        `json:"channel"`
			Text         string        `json:"text"`
			Attachements []attachments `json:"attachments"`
		}

		webhooks := slack{
			Username:  slackUsername,
			IconEmoji: iconEmoji,
			IconURL:   iconURL,
			Channel:   slackChannel,
			Text:      "VAddy Scan (Version " + VERSION + ")",
			Attachements: []attachments{
				{
					Color: "warning",
					Title: title,
					Text:  text,
				},
			},
		}

		params, _ := json.Marshal(webhooks)
		resp, err := http.PostForm(
			slackWebhookURL,
			url.Values{"payload": {string(params)}},
		)
		if err == nil {
			defer resp.Body.Close()
		}
	}
}

func detectApiVersion(project_id string) string {
	if len(project_id) > 0 {
		return "v2"
	}

	return "v1"
}
<|endoftext|>


<commit_before>package main

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

	"github.com/chromium/hstspreload"
	"github.com/chromium/hstspreload/chromiumpreload"
)

func main() {
	staticHandler := http.FileServer(http.Dir("static"))
	http.Handle("/", staticHandler)
	http.Handle("/style.css", staticHandler)
	http.Handle("/index.js", staticHandler)

	http.HandleFunc("/robots.txt", http.NotFound)
	http.HandleFunc("/favicon.ico", http.NotFound)

	http.HandleFunc("/checkdomain/", checkdomain)
	http.HandleFunc("/status/", status)

	http.HandleFunc("/submit/", submit)
	http.HandleFunc("/pending", pending)
	http.HandleFunc("/update", update)

	http.ListenAndServe(":8080", nil)
}

func checkdomain(w http.ResponseWriter, r *http.Request) {
	domain := r.URL.Path[len("/checkdomain/"):]

	issues := hstspreload.CheckDomain(domain)

	b, err := json.MarshalIndent(hstspreload.MakeSlices(issues), "", "  ")
	if err != nil {
		http.Error(w, "Internal error: could not encode JSON.", 500)
	} else {
		fmt.Fprintf(w, "%s\n", b)
	}
}

// writeJSON  should only be called if nothing has been written yet.
func writeJSONOrBust(w http.ResponseWriter, v interface{}) {
	b, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		msg := fmt.Sprintf("Internal error: could not format JSON. (%s)\n", err)
		http.Error(w, msg, 500)
		return
	}

	fmt.Fprintf(w, "%s\n", b)
	return
}

func status(w http.ResponseWriter, r *http.Request) {
	domain := chromiumpreload.Domain(r.URL.Path[len("/status/"):])

	state, err := stateForDomain(domain)
	if err != nil {
		msg := fmt.Sprintf("Internal error: could not retrieve status. (%s)\n", err)
		http.Error(w, msg, 500)
		return
	}

	domainStateJSON := struct {
		Name    chromiumpreload.Domain `json:"name"`
		Status  string                 `json:"status,name"`
		Message string                 `json:"messsage,omitempty"`
	}{
		Name:    domain,
		Status:  statusToString[state.Status],
		Message: state.Message,
	}

	writeJSONOrBust(w, domainStateJSON)
}

func submit(w http.ResponseWriter, r *http.Request) {
	domainStr := r.URL.Path[len("/submit/"):]
	domain := chromiumpreload.Domain(domainStr)

	issues := hstspreload.CheckDomain(domainStr)
	if len(issues.Errors) > 0 {
		writeJSONOrBust(w, issues)
		return
	}

	state, stateErr := stateForDomain(domain)
	if stateErr != nil {
		msg := fmt.Sprintf("Internal error: could not get current domain status. (%s)\n", stateErr)
		http.Error(w, msg, 500)
	}

	switch state.Status {
	case StatusUnknown:
		fallthrough
	case StatusRemoved:
		putErr := putState(DomainState{
			Name:   domain,
			Status: StatusPending,
		})
		if putErr != nil {
			issues = hstspreload.Issues{
				Errors:   append(issues.Errors, "Internal error: Unable to save to the pending list."),
				Warnings: issues.Warnings,
			}
		}
	case StatusPending:
		issues = hstspreload.Issues{
			Errors:   issues.Errors,
			Warnings: append(issues.Warnings, "Domain is already pending."),
		}
	case StatusPreloaded:
		issues = hstspreload.Issues{
			Errors:   append(issues.Errors, "Domain is already preloaded."),
			Warnings: issues.Warnings,
		}
	case StatusRejected:
		rejectedMsg := fmt.Sprintf("Domain has been rejected. (%s)", state.Message)
		issues = hstspreload.Issues{
			Errors:   append(issues.Warnings, rejectedMsg),
			Warnings: issues.Warnings,
		}
	default:
		issues = hstspreload.Issues{
			Errors:   append(issues.Warnings, "Cannot preload."),
			Warnings: issues.Warnings,
		}
	}

	writeJSONOrBust(w, hstspreload.MakeSlices(issues))
}

func pending(w http.ResponseWriter, r *http.Request) {
	names, err := domainsWithStatus(StatusPending)
	if err != nil {
		msg := fmt.Sprintf("Internal error: could not retrieve pending list. (%s)\n", err)
		http.Error(w, msg, 500)
		return
	}

	fmt.Fprintf(w, "[\n")
	for i, name := range names {
		comma := ","
		if i+1 == len(names) {
			comma = ""
		}

		fmt.Fprintf(w, `    { "name": "%s", "include_subdomains": true, "mode": "force-https" }%s
`, name, comma)
	}
	fmt.Fprintf(w, "]\n")
}

func difference(from []chromiumpreload.Domain, take []chromiumpreload.Domain) (diff []chromiumpreload.Domain) {
	takeSet := make(map[chromiumpreload.Domain]bool)
	for _, elem := range take {
		takeSet[elem] = true
	}

	for _, elem := range from {
		if !takeSet[elem] {
			diff = append(diff, elem)
		}
	}

	return diff
}

func update(w http.ResponseWriter, r *http.Request) {
	// Get preload list.
	preloadList, listErr := chromiumpreload.GetLatest()
	if listErr != nil {
		msg := fmt.Sprintf(
			"Internal error: could not retrieve latest preload list. (%s)\b",
			listErr,
		)
		http.Error(w, msg, 500)
		return
	}
	var actualPreload []chromiumpreload.Domain
	for _, entry := range preloadList.Entries {
		if entry.Mode == chromiumpreload.ForceHTTPS {
			actualPreload = append(actualPreload, entry.Name)
		}
	}

	// Get domains currently recorded as preloaded.
	databasePreload, dbErr := domainsWithStatus(StatusPreloaded)
	if dbErr != nil {
		msg := fmt.Sprintf(
			"Internal error: could not retrieve domain names previously marked as preloaded. (%s)\n",
			dbErr,
		)
		http.Error(w, msg, 500)
		return
	}

	// Calculate values that are out of date.
	var updates []DomainState

	added := difference(actualPreload, databasePreload)
	for _, name := range added {
		updates = append(updates, DomainState{
			Name:   name,
			Status: StatusPreloaded,
		})
	}

	removed := difference(databasePreload, actualPreload)
	for _, name := range removed {
		updates = append(updates, DomainState{
			Name:   name,
			Status: StatusRemoved,
		})
	}

	fmt.Fprintf(w, `The preload list has %d entries.
- # of preloaded HSTS entries: %d
- # to be added in this update: %d
- # to be removed this update: %d
`,
		len(preloadList.Entries),
		len(actualPreload),
		len(added),
		len(removed),
	)

	// Create statusReport function to show progress.
	written := false
	f, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Internal error: Could not create `http.Flusher`.", 500)
		return
	}
	statusReport := func(format string, args ...interface{}) {
		fmt.Fprintf(w, format, args...)
		f.Flush()
		written = true
	}

	// Update the database
	putErr := putStates(updates, statusReport)
	if putErr != nil {
		msg := fmt.Sprintf(
			"Internal error: datastore update failed. (%s)\n",
			putErr,
		)
		if written {
			// The header has already been sent, so we can't return 500.
			fmt.Fprintf(w, msg)
		} else {
			http.Error(w, msg, 500)
		}
		return
	}

	fmt.Fprintf(w, "Success. %d domain states updated.\n", len(updates))
}
<commit_msg>Remove extraneous annotation in states handler.<commit_after>package main

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

	"github.com/chromium/hstspreload"
	"github.com/chromium/hstspreload/chromiumpreload"
)

func main() {
	staticHandler := http.FileServer(http.Dir("static"))
	http.Handle("/", staticHandler)
	http.Handle("/style.css", staticHandler)
	http.Handle("/index.js", staticHandler)

	http.HandleFunc("/robots.txt", http.NotFound)
	http.HandleFunc("/favicon.ico", http.NotFound)

	http.HandleFunc("/checkdomain/", checkdomain)
	http.HandleFunc("/status/", status)

	http.HandleFunc("/submit/", submit)
	http.HandleFunc("/pending", pending)
	http.HandleFunc("/update", update)

	http.ListenAndServe(":8080", nil)
}

func checkdomain(w http.ResponseWriter, r *http.Request) {
	domain := r.URL.Path[len("/checkdomain/"):]

	issues := hstspreload.CheckDomain(domain)

	b, err := json.MarshalIndent(hstspreload.MakeSlices(issues), "", "  ")
	if err != nil {
		http.Error(w, "Internal error: could not encode JSON.", 500)
	} else {
		fmt.Fprintf(w, "%s\n", b)
	}
}

// writeJSON  should only be called if nothing has been written yet.
func writeJSONOrBust(w http.ResponseWriter, v interface{}) {
	b, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		msg := fmt.Sprintf("Internal error: could not format JSON. (%s)\n", err)
		http.Error(w, msg, 500)
		return
	}

	fmt.Fprintf(w, "%s\n", b)
	return
}

func status(w http.ResponseWriter, r *http.Request) {
	domain := chromiumpreload.Domain(r.URL.Path[len("/status/"):])

	state, err := stateForDomain(domain)
	if err != nil {
		msg := fmt.Sprintf("Internal error: could not retrieve status. (%s)\n", err)
		http.Error(w, msg, 500)
		return
	}

	domainStateJSON := struct {
		Name    chromiumpreload.Domain `json:"name"`
		Status  string                 `json:"status"`
		Message string                 `json:"messsage,omitempty"`
	}{
		Name:    domain,
		Status:  statusToString[state.Status],
		Message: state.Message,
	}

	writeJSONOrBust(w, domainStateJSON)
}

func submit(w http.ResponseWriter, r *http.Request) {
	domainStr := r.URL.Path[len("/submit/"):]
	domain := chromiumpreload.Domain(domainStr)

	issues := hstspreload.CheckDomain(domainStr)
	if len(issues.Errors) > 0 {
		writeJSONOrBust(w, issues)
		return
	}

	state, stateErr := stateForDomain(domain)
	if stateErr != nil {
		msg := fmt.Sprintf("Internal error: could not get current domain status. (%s)\n", stateErr)
		http.Error(w, msg, 500)
	}

	switch state.Status {
	case StatusUnknown:
		fallthrough
	case StatusRemoved:
		putErr := putState(DomainState{
			Name:   domain,
			Status: StatusPending,
		})
		if putErr != nil {
			issues = hstspreload.Issues{
				Errors:   append(issues.Errors, "Internal error: Unable to save to the pending list."),
				Warnings: issues.Warnings,
			}
		}
	case StatusPending:
		issues = hstspreload.Issues{
			Errors:   issues.Errors,
			Warnings: append(issues.Warnings, "Domain is already pending."),
		}
	case StatusPreloaded:
		issues = hstspreload.Issues{
			Errors:   append(issues.Errors, "Domain is already preloaded."),
			Warnings: issues.Warnings,
		}
	case StatusRejected:
		rejectedMsg := fmt.Sprintf("Domain has been rejected. (%s)", state.Message)
		issues = hstspreload.Issues{
			Errors:   append(issues.Warnings, rejectedMsg),
			Warnings: issues.Warnings,
		}
	default:
		issues = hstspreload.Issues{
			Errors:   append(issues.Warnings, "Cannot preload."),
			Warnings: issues.Warnings,
		}
	}

	writeJSONOrBust(w, hstspreload.MakeSlices(issues))
}

func pending(w http.ResponseWriter, r *http.Request) {
	names, err := domainsWithStatus(StatusPending)
	if err != nil {
		msg := fmt.Sprintf("Internal error: could not retrieve pending list. (%s)\n", err)
		http.Error(w, msg, 500)
		return
	}

	fmt.Fprintf(w, "[\n")
	for i, name := range names {
		comma := ","
		if i+1 == len(names) {
			comma = ""
		}

		fmt.Fprintf(w, `    { "name": "%s", "include_subdomains": true, "mode": "force-https" }%s
`, name, comma)
	}
	fmt.Fprintf(w, "]\n")
}

func difference(from []chromiumpreload.Domain, take []chromiumpreload.Domain) (diff []chromiumpreload.Domain) {
	takeSet := make(map[chromiumpreload.Domain]bool)
	for _, elem := range take {
		takeSet[elem] = true
	}

	for _, elem := range from {
		if !takeSet[elem] {
			diff = append(diff, elem)
		}
	}

	return diff
}

func update(w http.ResponseWriter, r *http.Request) {
	// Get preload list.
	preloadList, listErr := chromiumpreload.GetLatest()
	if listErr != nil {
		msg := fmt.Sprintf(
			"Internal error: could not retrieve latest preload list. (%s)\b",
			listErr,
		)
		http.Error(w, msg, 500)
		return
	}
	var actualPreload []chromiumpreload.Domain
	for _, entry := range preloadList.Entries {
		if entry.Mode == chromiumpreload.ForceHTTPS {
			actualPreload = append(actualPreload, entry.Name)
		}
	}

	// Get domains currently recorded as preloaded.
	databasePreload, dbErr := domainsWithStatus(StatusPreloaded)
	if dbErr != nil {
		msg := fmt.Sprintf(
			"Internal error: could not retrieve domain names previously marked as preloaded. (%s)\n",
			dbErr,
		)
		http.Error(w, msg, 500)
		return
	}

	// Calculate values that are out of date.
	var updates []DomainState

	added := difference(actualPreload, databasePreload)
	for _, name := range added {
		updates = append(updates, DomainState{
			Name:   name,
			Status: StatusPreloaded,
		})
	}

	removed := difference(databasePreload, actualPreload)
	for _, name := range removed {
		updates = append(updates, DomainState{
			Name:   name,
			Status: StatusRemoved,
		})
	}

	fmt.Fprintf(w, `The preload list has %d entries.
- # of preloaded HSTS entries: %d
- # to be added in this update: %d
- # to be removed this update: %d
`,
		len(preloadList.Entries),
		len(actualPreload),
		len(added),
		len(removed),
	)

	// Create statusReport function to show progress.
	written := false
	f, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Internal error: Could not create `http.Flusher`.", 500)
		return
	}
	statusReport := func(format string, args ...interface{}) {
		fmt.Fprintf(w, format, args...)
		f.Flush()
		written = true
	}

	// Update the database
	putErr := putStates(updates, statusReport)
	if putErr != nil {
		msg := fmt.Sprintf(
			"Internal error: datastore update failed. (%s)\n",
			putErr,
		)
		if written {
			// The header has already been sent, so we can't return 500.
			fmt.Fprintf(w, msg)
		} else {
			http.Error(w, msg, 500)
		}
		return
	}

	fmt.Fprintf(w, "Success. %d domain states updated.\n", len(updates))
}
<|endoftext|>


<commit_before>package models

import "github.com/UniversityRadioYork/myradio-go"

// PodcastModel is the model for the Podcast controller.
type PodcastModel struct {
	Model
}

// NewPodcastModel returns a new PodcastModel on the MyRadio session s.
func NewPodcastModel(s *myradio.Session) *PodcastModel {
	return &PodcastModel{Model{session: s}}
}

// GetAllPodcasts gets the data required for the Podcast controller from MyRadio.
//
// On success, it returns the podcasts and nil
// Otherwise, it returns undefined data and the error causing failure.
func (m *PodcastModel) GetAllPodcasts(number int, page int) (podcasts []myradio.Podcast, err error) {
	//Get 'number' of podcasts from page 'page' (0 = the latest podcasts)
	allpodcasts := make([]myradio.Podcast, number)
	allpodcasts, err = m.session.GetAllPodcasts(number, page)
	if err != nil {
		return
	}

	for i, p := range allpodcasts {
		if p.Status == "Published" {
			podcasts = append(podcasts, allpodcasts[i])
		}
	}

	return
}

// Get gets the data required for the Podcast controller from MyRadio.
//
// On success, it returns the users name, bio, a list of officerships, their photo if they have one and nil
// Otherwise, it returns undefined data and the error causing failure.
func (m *PodcastModel) Get(id int) (podcast *myradio.Podcast, err error) {
	podcast, err = m.session.Get(id)
	if err != nil {
		return
	}

	return
}
<commit_msg>Removed unnessesary error handling<commit_after>package models

import "github.com/UniversityRadioYork/myradio-go"

// PodcastModel is the model for the Podcast controller.
type PodcastModel struct {
	Model
}

// NewPodcastModel returns a new PodcastModel on the MyRadio session s.
func NewPodcastModel(s *myradio.Session) *PodcastModel {
	return &PodcastModel{Model{session: s}}
}

// GetAllPodcasts gets the data required for the Podcast controller from MyRadio.
//
// On success, it returns the podcasts and nil
// Otherwise, it returns undefined data and the error causing failure.
func (m *PodcastModel) GetAllPodcasts(number int, page int) (podcasts []myradio.Podcast, err error) {
	//Get 'number' of podcasts from page 'page' (0 = the latest podcasts)
	allpodcasts := make([]myradio.Podcast, number)
	allpodcasts, err = m.session.GetAllPodcasts(number, page)
	if err != nil {
		return
	}

	for i, p := range allpodcasts {
		if p.Status == "Published" {
			podcasts = append(podcasts, allpodcasts[i])
		}
	}

	return
}

// Get gets the data required for the Podcast controller from MyRadio.
//
// On success, it returns the users name, bio, a list of officerships, their photo if they have one and nil
// Otherwise, it returns undefined data and the error causing failure.
func (m *PodcastModel) Get(id int) (podcast *myradio.Podcast, err error) {
	return m.session.Get(id)
}
<|endoftext|>


<commit_before>package models

import (
	"github.com/asaskevich/govalidator"

	"github.com/techjanitor/pram-post/config"
	e "github.com/techjanitor/pram-post/errors"
	u "github.com/techjanitor/pram-post/utils"
)

// Register contains information for initial account creation
type RegisterModel struct {
	Name     string
	Email    string
	Password string
	Hashed   []byte
}

// Validate will check the provided name length and email
func (r *RegisterModel) Validate() (err error) {

	// Validate name input
	name := u.Validate{Input: r.Name, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if name.IsEmpty() {
		return e.ErrNameEmpty
	} else if name.MinLength() {
		return e.ErrNameShort
	} else if name.MaxLength() {
		return e.ErrNameLong
	} else if !name.IsUsername() {
		return e.ErrNameAlphaNum
	}

	// Validate password input
	password := u.Validate{Input: r.Password, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if password.IsEmpty() {
		return e.ErrPasswordEmpty
	} else if password.MinLength() {
		return e.ErrPasswordShort
	} else if password.MaxLength() {
		return e.ErrPasswordLong
	}

	// if theres an email validate it
	if r.Email != "" {
		// Validate email
		if !govalidator.IsEmail(r.Email) {
			return e.ErrInvalidEmail
		}
	}
	return

}

// check for duplicate name before registering
func (r *RegisterModel) CheckDuplicate() (err error) {

	// Get Database handle
	db, err := u.GetDb()
	if err != nil {
		return
	}

	var check bool

	err = db.QueryRow("select count(*) from users where user_name = ?", r.Name).Scan(&check)
	if err != nil {
		return
	}

	// Error if it does
	if check {
		return e.ErrDuplicateName
	}

	return

}

// register new user
func (r *RegisterModel) Register() (err error) {

	// Get Database handle
	db, err := u.GetDb()
	if err != nil {
		return
	}

	ps1, err := db.Prepare("INSERT into users (usergroup_id, user_name, user_email, user_password, user_confirmed) VALUES (?,?,?,?,?)")
	if err != nil {
		return
	}
	defer ps1.Close()

	_, err = ps1.Exec(2, r.Name, r.Email, r.Hashed, 1)
	if err != nil {
		return
	}

	return

}
<commit_msg>add avatar<commit_after>package models

import (
	"github.com/asaskevich/govalidator"

	"github.com/techjanitor/pram-post/config"
	e "github.com/techjanitor/pram-post/errors"
	u "github.com/techjanitor/pram-post/utils"
)

// Register contains information for initial account creation
type RegisterModel struct {
	Name     string
	Email    string
	Password string
	Hashed   []byte
}

// Validate will check the provided name length and email
func (r *RegisterModel) Validate() (err error) {

	// Validate name input
	name := u.Validate{Input: r.Name, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if name.IsEmpty() {
		return e.ErrNameEmpty
	} else if name.MinLength() {
		return e.ErrNameShort
	} else if name.MaxLength() {
		return e.ErrNameLong
	} else if !name.IsUsername() {
		return e.ErrNameAlphaNum
	}

	// Validate password input
	password := u.Validate{Input: r.Password, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if password.IsEmpty() {
		return e.ErrPasswordEmpty
	} else if password.MinLength() {
		return e.ErrPasswordShort
	} else if password.MaxLength() {
		return e.ErrPasswordLong
	}

	// if theres an email validate it
	if r.Email != "" {
		// Validate email
		if !govalidator.IsEmail(r.Email) {
			return e.ErrInvalidEmail
		}
	}
	return

}

// check for duplicate name before registering
func (r *RegisterModel) CheckDuplicate() (err error) {

	// Get Database handle
	db, err := u.GetDb()
	if err != nil {
		return
	}

	var check bool

	err = db.QueryRow("select count(*) from users where user_name = ?", r.Name).Scan(&check)
	if err != nil {
		return
	}

	// Error if it does
	if check {
		return e.ErrDuplicateName
	}

	return

}

// register new user
func (r *RegisterModel) Register() (err error) {

	// Get Database handle
	db, err := u.GetDb()
	if err != nil {
		return
	}

	ps1, err := db.Prepare("INSERT into users (usergroup_id, user_name, user_email, user_password, user_confirmed, user_avatar) VALUES (?,?,?,?,?,ROUND((RAND() * (48-1))+1))")
	if err != nil {
		return
	}
	defer ps1.Close()

	_, err = ps1.Exec(2, r.Name, r.Email, r.Hashed, 1)
	if err != nil {
		return
	}

	return

}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"github.com/asaskevich/govalidator"

	"github.com/eirka/eirka-libs/config"
	"github.com/eirka/eirka-libs/db"
	e "github.com/eirka/eirka-libs/errors"
	"github.com/eirka/eirka-libs/validate"
)

// Register contains information for initial account creation
type RegisterModel struct {
	Name     string
	Email    string
	Password string
	Hashed   []byte
}

// check struct validity
func (r *RegisterModel) IsValid() bool {

	if r.Name == "" {
		return false
	}

	if r.Password == "" {
		return false
	}

	if r.Hashed == nil {
		return false
	}

	return true

}

// Validate will check the provided name length and email
func (r *RegisterModel) Validate() (err error) {

	// Validate name input
	name := validate.Validate{Input: r.Name, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if name.IsEmpty() {
		return e.ErrNameEmpty
	} else if name.MinLength() {
		return e.ErrNameShort
	} else if name.MaxLength() {
		return e.ErrNameLong
	} else if !name.IsUsername() {
		return e.ErrNameAlphaNum
	}

	// Validate password input
	password := validate.Validate{Input: r.Password, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if password.IsEmpty() {
		return e.ErrPasswordEmpty
	} else if password.MinLength() {
		return e.ErrPasswordShort
	} else if password.MaxLength() {
		return e.ErrPasswordLong
	}

	// if theres an email validate it
	if r.Email != "" {
		// Validate email
		if !govalidator.IsEmail(r.Email) {
			return e.ErrInvalidEmail
		}
	}
	return

}

// register new user
func (r *RegisterModel) Register() (err error) {

	if r.Hashed == nil {
		return e.ErrPasswordEmpty
	}

	// check model validity
	if !r.IsValid() {
		return errors.New("RegisterModel is not valid")
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	ps1, err := dbase.Prepare("INSERT into users (user_name, user_email, user_password, user_confirmed, user_avatar) VALUES (?,?,?,?,ROUND((RAND() * (48-1))+1))")
	if err != nil {
		return
	}
	defer ps1.Close()

	e1, err := ps1.Exec(r.Name, r.Email, r.Hashed, 1)
	if err != nil {
		return
	}

	user_id, err := e1.LastInsertId()
	if err != nil {
		return err
	}

	ps2, err := dbase.Prepare("INSERT into user_role_map VALUES (?,?)")
	if err != nil {
		return
	}
	defer ps1.Close()

	_, err = ps2.Exec(user_id, 2)
	if err != nil {
		return
	}

	return

}
<commit_msg>new amazon library<commit_after>package models

import (
	"errors"
	"github.com/asaskevich/govalidator"

	"github.com/eirka/eirka-libs/config"
	"github.com/eirka/eirka-libs/db"
	e "github.com/eirka/eirka-libs/errors"
	"github.com/eirka/eirka-libs/validate"
)

// Register contains information for initial account creation
type RegisterModel struct {
	Name     string
	Email    string
	Password string
	Hashed   []byte
}

// check struct validity
func (r *RegisterModel) IsValid() bool {

	if r.Name == "" {
		return false
	}

	if r.Password == "" {
		return false
	}

	if r.Hashed == nil {
		return false
	}

	return true

}

// Validate will check the provided name length and email
func (r *RegisterModel) Validate() (err error) {

	// Validate name input
	name := validate.Validate{Input: r.Name, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if name.IsEmpty() {
		return e.ErrNameEmpty
	} else if name.MinLength() {
		return e.ErrNameShort
	} else if name.MaxLength() {
		return e.ErrNameLong
	}

	// Validate password input
	password := validate.Validate{Input: r.Password, Max: config.Settings.Limits.NameMaxLength, Min: config.Settings.Limits.NameMinLength}
	if password.IsEmpty() {
		return e.ErrPasswordEmpty
	} else if password.MinLength() {
		return e.ErrPasswordShort
	} else if password.MaxLength() {
		return e.ErrPasswordLong
	}

	// if theres an email validate it
	if r.Email != "" {
		// Validate email
		if !govalidator.IsEmail(r.Email) {
			return e.ErrInvalidEmail
		}
	}
	return

}

// register new user
func (r *RegisterModel) Register() (err error) {

	if r.Hashed == nil {
		return e.ErrPasswordEmpty
	}

	// check model validity
	if !r.IsValid() {
		return errors.New("RegisterModel is not valid")
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	ps1, err := dbase.Prepare("INSERT into users (user_name, user_email, user_password, user_confirmed, user_avatar) VALUES (?,?,?,?,ROUND((RAND() * (48-1))+1))")
	if err != nil {
		return
	}
	defer ps1.Close()

	e1, err := ps1.Exec(r.Name, r.Email, r.Hashed, 1)
	if err != nil {
		return
	}

	user_id, err := e1.LastInsertId()
	if err != nil {
		return err
	}

	ps2, err := dbase.Prepare("INSERT into user_role_map VALUES (?,?)")
	if err != nil {
		return
	}
	defer ps1.Close()

	_, err = ps2.Exec(user_id, 2)
	if err != nil {
		return
	}

	return

}
<|endoftext|>


<commit_before>package testdata

import (
	"errors"
	"regexp"
)

func unused() {
	regexp.Compile(".+")

	if errors.New("abc") == errors.New("abc") {
		// test SA4000
	}
}
<commit_msg>update test data<commit_after>package testdata

import (
	"errors"
)

func unused() {
	if errors.New("abc") == errors.New("abc") {
		// test SA4000
	}
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"image/png"
	"math"
	"os"
	"strings"
	"time"
)

// Approx. framerate of the display
const fps = 30

// How long each frame is displayed for
const frameTime = time.Second / fps

// If a frame's delay is under this duration, we will display it
// for a certain number of frames, rather than for a time.
const adjustDelayUnder = time.Millisecond * 300

type Image interface {
	GetNextFrame() *image.RGBA
	GetPositionFrame(position float64, blend bool) *image.RGBA
}

type SingleImage struct {
	frame *image.RGBA
}

func NewSingleImage(frame *image.RGBA) *SingleImage {
	return &SingleImage{frame}
}

func (i *SingleImage) GetNextFrame() *image.RGBA {
	return i.frame
}

func (i *SingleImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
	return i.frame
}

type AnimatedImage struct {
	frameRequest    chan bool
	pos             int
	frames          []*image.RGBA
	delays          []int
	remainingLoops  int
	started         bool
	delayAdjustment float64
}

func (i *AnimatedImage) GetNextFrame() *image.RGBA {

	if !i.started {
		i.started = true
		i.start()
	}

	frame := i.frames[i.pos]

	select {
	case i.frameRequest <- true:
	default:
	}

	return frame
}

func (i *AnimatedImage) start() {

	go func() {
		for {
			delay := time.Duration(float64(i.delays[i.pos])*i.delayAdjustment) * 10 * time.Millisecond

			if delay < adjustDelayUnder {

				// Rounded to nearest int
				framesToDisplay := int(math.Floor((float64(delay) / float64(frameTime)) + 0.5))

				// Show for at least one frame
				if framesToDisplay == 0 {
					framesToDisplay = 1
				}

				//log.Printf("Frame wanted a delay of %d so showing for %d frames", delay, framesToDisplay)

				for x := 0; x < framesToDisplay; x++ {
					// Wait till this frame has been taken
					<-i.frameRequest
				}

			} else {
				// Just sleep. At these amounts noone will notice +-1 frame
				//log.Printf("Sleeping frame %d for %d", i.pos, delay)
				time.Sleep(delay)
			}

			i.pos++

			if i.pos >= len(i.frames) {
				i.pos = 0
				i.remainingLoops--
			}

			if i.remainingLoops == 0 {
				break
			}
		}
	}()

}

// GetPositionFrame returns the frame corresponding to the position given 0....1
func (i *AnimatedImage) GetPositionFrame(position float64, blend bool) *image.RGBA {

	relativePosition := position * float64(len(i.frames)-1)

	previousFrame := int(math.Floor(relativePosition))
	nextFrame := int(math.Ceil(relativePosition))

	framePosition := math.Mod(relativePosition, 1)

	//log.Debugf("GetPositionFrame. Frames:%d Position:%f RelativePosition:%f FramePosition:%f PreviousFrame:%d NextFrame:%d", len(i.frames), position, relativePosition, framePosition, previousFrame, nextFrame)
	if !blend || previousFrame == nextFrame {
		// We can just send back a single frame
		return i.frames[previousFrame]
	}

	maskNext := image.NewUniform(color.Alpha{uint8(255 * framePosition)})

	frame := image.NewRGBA(image.Rect(0, 0, 16, 16))

	draw.Draw(frame, frame.Bounds(), i.frames[previousFrame], image.Point{0, 0}, draw.Src)
	draw.DrawMask(frame, frame.Bounds(), i.frames[nextFrame], image.Point{0, 0}, maskNext, image.Point{0, 0}, draw.Over)

	return frame
}

func LoadImage(src string) Image {
	srcLower := strings.ToLower(src)

	if strings.Contains(srcLower, ".gif") {
		return LoadGif(src)
	} else if strings.Contains(srcLower, ".png") {
		return LoadPng(src)
	} else {
		log.HandleError(fmt.Errorf(src), "Unknown image format")
	}
	return nil
}

func LoadPng(src string) Image {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open png '%s' : %s", src, err)
	}

	img, err := png.Decode(file)
	if err != nil {
		log.Fatalf("PNG decoding failed on image '%s' : %s", src, err)
	}

	return &SingleImage{
		frame: toRGBA(img),
	}
}

func LoadGif(src string) *AnimatedImage {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open gif '%s' : %s", src, err)
	}

	img, err := gif.DecodeAll(file)
	if err != nil {
		log.Fatalf("Gif decoding failed on image '%s' : %s", src, err)
	}

	var frames = []*image.RGBA{}

	for _, frame := range img.Image {
		frames = append(frames, toRGBA(frame))
	}

	return &AnimatedImage{
		frames:          frames,
		delays:          img.Delay,
		remainingLoops:  img.LoopCount,
		frameRequest:    make(chan bool),
		delayAdjustment: 1.0,
	}
}

func toRGBA(in image.Image) *image.RGBA {
	bounds := in.Bounds()
	out := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
	draw.Draw(out, out.Bounds(), in, bounds.Min, draw.Over)
	return out
}
<commit_msg>Nicer looping code. That might actually work?<commit_after>package util

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"image/png"
	"math"
	"os"
	"strings"
	"time"
)

// Approx. framerate of the display
const fps = 30

// How long each frame is displayed for
const frameTime = time.Second / fps

// If a frame's delay is under this duration, we will display it
// for a certain number of frames, rather than for a time.
const adjustDelayUnder = time.Millisecond * 300

type Image interface {
	GetNextFrame() *image.RGBA
	GetPositionFrame(position float64, blend bool) *image.RGBA
}

type SingleImage struct {
	frame *image.RGBA
}

func NewSingleImage(frame *image.RGBA) *SingleImage {
	return &SingleImage{frame}
}

func (i *SingleImage) GetNextFrame() *image.RGBA {
	return i.frame
}

func (i *SingleImage) GetPositionFrame(position float64, blend bool) *image.RGBA {
	return i.frame
}

type AnimatedImage struct {
	frameRequest    chan bool
	pos             int
	frames          []*image.RGBA
	delays          []int
	remainingLoops  int
	started         bool
	delayAdjustment float64
}

func (i *AnimatedImage) GetNextFrame() *image.RGBA {

	if !i.started {
		i.started = true
		i.start()
	}

	frame := i.frames[i.pos]

	select {
	case i.frameRequest <- true:
	default:
	}

	return frame
}

func (i *AnimatedImage) start() {

	go func() {
		for {
			delay := time.Duration(float64(i.delays[i.pos])*i.delayAdjustment) * 10 * time.Millisecond

			if delay < adjustDelayUnder {

				// Rounded to nearest int
				framesToDisplay := int(math.Floor((float64(delay) / float64(frameTime)) + 0.5))

				// Show for at least one frame
				if framesToDisplay == 0 {
					framesToDisplay = 1
				}

				//log.Printf("Frame wanted a delay of %d so showing for %d frames", delay, framesToDisplay)

				for x := 0; x < framesToDisplay; x++ {
					// Wait till this frame has been taken
					<-i.frameRequest
				}

			} else {
				// Just sleep. At these amounts noone will notice +-1 frame
				//log.Printf("Sleeping frame %d for %d", i.pos, delay)
				time.Sleep(delay)
			}

			// That was the last frame
			if i.pos == len(i.frames)-1 {

				if i.remainingLoops > 0 {
					// Start again, we still have at least one loop remaining
					i.pos = 0
					i.remainingLoops--
				} else {
					// We're done, this frame gets shown forever *drops mic*.
					break
				}
			}

		}
	}()

}

// GetPositionFrame returns the frame corresponding to the position given 0....1
func (i *AnimatedImage) GetPositionFrame(position float64, blend bool) *image.RGBA {

	relativePosition := position * float64(len(i.frames)-1)

	previousFrame := int(math.Floor(relativePosition))
	nextFrame := int(math.Ceil(relativePosition))

	framePosition := math.Mod(relativePosition, 1)

	//log.Debugf("GetPositionFrame. Frames:%d Position:%f RelativePosition:%f FramePosition:%f PreviousFrame:%d NextFrame:%d", len(i.frames), position, relativePosition, framePosition, previousFrame, nextFrame)
	if !blend || previousFrame == nextFrame {
		// We can just send back a single frame
		return i.frames[previousFrame]
	}

	maskNext := image.NewUniform(color.Alpha{uint8(255 * framePosition)})

	frame := image.NewRGBA(image.Rect(0, 0, 16, 16))

	draw.Draw(frame, frame.Bounds(), i.frames[previousFrame], image.Point{0, 0}, draw.Src)
	draw.DrawMask(frame, frame.Bounds(), i.frames[nextFrame], image.Point{0, 0}, maskNext, image.Point{0, 0}, draw.Over)

	return frame
}

func LoadImage(src string) Image {
	srcLower := strings.ToLower(src)

	if strings.Contains(srcLower, ".gif") {
		return LoadGif(src)
	} else if strings.Contains(srcLower, ".png") {
		return LoadPng(src)
	} else {
		log.HandleError(fmt.Errorf(src), "Unknown image format")
	}
	return nil
}

func LoadPng(src string) Image {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open png '%s' : %s", src, err)
	}

	img, err := png.Decode(file)
	if err != nil {
		log.Fatalf("PNG decoding failed on image '%s' : %s", src, err)
	}

	return &SingleImage{
		frame: toRGBA(img),
	}
}

func LoadGif(src string) *AnimatedImage {

	file, err := os.Open(src)

	if err != nil {
		log.Fatalf("Could not open gif '%s' : %s", src, err)
	}

	img, err := gif.DecodeAll(file)
	if err != nil {
		log.Fatalf("Gif decoding failed on image '%s' : %s", src, err)
	}

	var frames = []*image.RGBA{}

	for _, frame := range img.Image {
		frames = append(frames, toRGBA(frame))
	}

	return &AnimatedImage{
		frames:          frames,
		delays:          img.Delay,
		remainingLoops:  img.LoopCount,
		frameRequest:    make(chan bool),
		delayAdjustment: 1.0,
	}
}

func toRGBA(in image.Image) *image.RGBA {
	bounds := in.Bounds()
	out := image.NewRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
	draw.Draw(out, out.Bounds(), in, bounds.Min, draw.Over)
	return out
}
<|endoftext|>


<commit_before>package app

import (
	"gnd.la/mail"
	"io"
)

type mailTemplate struct {
	tmpl *tmpl
	ctx  *Context
}

func (t *mailTemplate) Execute(w io.Writer, data interface{}) error {
	return t.tmpl.executeContext(t.ctx, w, "", data, nil)
}

// MailTemplate returns the given template as mail.Template which can be used
// to send App templates with gnd.la/mail.Send. Don't try to use a Template
// as returned by App.LoadTemplate, since functions like reverse or translations
// will not work otherwise because they won't have access to the *Context. Note
// that the lifetime of the returned mail.Template is tied to the lifetime
// of the *Context.
func (c *Context) MailTemplate(template string) (mail.Template, error) {
	t, err := c.app.LoadTemplate(template)
	if err != nil {
		return nil, err
	}
	return &mailTemplate{tmpl: t.(*tmpl), ctx: c}, nil
}

// SendMail is a shorthand function for sending an email from a template. If you need
// more granularity you can use Context.MailTemplate and gnd.la/mail.Send directly.
// Note that fields other than Message and Data in the opts argument are not altered
// and are passed as is to gnd.la/mail.Send.
func (c *Context) SendMail(to []string, template string, data interface{}, opts *mail.Options) error {
	if template != "" {
		t, err := c.MailTemplate(template)
		if err != nil {
			return err
		}
		if opts == nil {
			opts = &mail.Options{}
		}
		opts.Message = t
		opts.Data = data
	}
	return mail.Send(to, opts)
}

// MustSendMail works like SendMail, but panics if there's an error.
func (c *Context) MustSendMail(to []string, template string, data interface{}, opts *mail.Options) {
	if err := c.SendMail(to, template, data, opts); err != nil {
		panic(err)
	}
}
<commit_msg>Allow to in SendMail to be a string or a []string<commit_after>package app

import (
	"fmt"
	"gnd.la/mail"
	"io"
)

type mailTemplate struct {
	tmpl *tmpl
	ctx  *Context
}

func (t *mailTemplate) Execute(w io.Writer, data interface{}) error {
	return t.tmpl.executeContext(t.ctx, w, "", data, nil)
}

// MailTemplate returns the given template as mail.Template which can be used
// to send App templates with gnd.la/mail.Send. Don't try to use a Template
// as returned by App.LoadTemplate, since functions like reverse or translations
// will not work otherwise because they won't have access to the *Context. Note
// that the lifetime of the returned mail.Template is tied to the lifetime
// of the *Context.
func (c *Context) MailTemplate(template string) (mail.Template, error) {
	t, err := c.app.LoadTemplate(template)
	if err != nil {
		return nil, err
	}
	return &mailTemplate{tmpl: t.(*tmpl), ctx: c}, nil
}

// SendMail is a shorthand function for sending an email from a template.
// The to parameter might be either a []string or a string, in the latter case
// it's parsed with gnd.la/mail.ParseAddressList.
//
// If you need more granularity you can use Context.MailTemplate and gnd.la/mail.Send
// directly. Note that fields other than Message and Data in the opts argument are not
// altered and are passed as is to gnd.la/mail.Send.
func (c *Context) SendMail(to interface{}, template string, data interface{}, opts *mail.Options) error {
	if template != "" {
		t, err := c.MailTemplate(template)
		if err != nil {
			return err
		}
		if opts == nil {
			opts = &mail.Options{}
		}
		opts.Message = t
		opts.Data = data
	}
	var addrs []string
	switch x := to.(type) {
	case string:
		p, err := mail.ParseAddressList(x)
		if err != nil {
			return err
		}
		addrs = p
	case []string:
		addrs = x
	default:
		return fmt.Errorf("invalid to type %T (%v)", to, to)
	}
	return mail.Send(addrs, opts)
}

// MustSendMail works like SendMail, but panics if there's an error.
func (c *Context) MustSendMail(to interface{}, template string, data interface{}, opts *mail.Options) {
	if err := c.SendMail(to, template, data, opts); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// continuous-deploy monitors pubsub events for the GCP Container Builder and
// pushes updated images when successful images are built.

package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strings"

	"cloud.google.com/go/pubsub"
	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/exec"
	"go.skia.org/infra/go/gitauth"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
	cloudbuild "google.golang.org/api/cloudbuild/v1"
	"google.golang.org/api/option"
)

// flags
var (
	local    = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
	project  = flag.String("project", "skia-public", "The GCE project name.")
	promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
)

var (
	parseImageName *regexp.Regexp
)

func Init() {
	parseImageName = regexp.MustCompile("^gcr.io/" + *project + "/([^:]+).*$")
}

// baseImageName returns "fiddler" from "gcr.io/skia-public/fiddler:foo".
//
// If the image name doesn't start with "gcr.io" and the project name then "" is returned.
func baseImageName(s string) string {
	matches := parseImageName.FindStringSubmatch(s)
	if len(matches) != 2 {
		return ""
	} else {
		return matches[1]
	}
}

// imagesFromInfo parses the incoming PubSub Data 'b' as JSON and then returns
// the full image names of all the images that match 'shortImageNames'.
func imagesFromInfo(shortImageNames []string, buildInfo cloudbuild.Build) []string {
	imageNames := []string{}
	for _, im := range buildInfo.Results.Images {
		sklog.Infof("ImageName: %s", im.Name)
		// Is this one of the images we are pushing?
		for _, name := range shortImageNames {
			if baseImageName(im.Name) == name {
				imageNames = append(imageNames, im.Name)
				break
			}
		}
	}
	return imageNames
}

func main() {
	common.InitWithMust(
		"continuous-deploy",
		common.PrometheusOpt(promPort),
	)
	if len(flag.Args()) == 0 {
		sklog.Fatal("continuous-deploy must be passed in at least one package name to push.")
	}
	Init()
	sklog.Infof("Pushing to: %v", flag.Args())
	ctx := context.Background()
	ts, err := auth.NewDefaultTokenSource(*local, pubsub.ScopePubSub, "https://www.googleapis.com/auth/gerritcodereview")
	if err != nil {
		sklog.Fatal(err)
	}
	if !*local {
		_, err := gitauth.New(ts, "/tmp/git-cookie", true, "skia-continuous-deploy@skia-public.iam.gserviceaccount.com")
		if err != nil {
			sklog.Fatal(err)
		}
	}
	client, err := pubsub.NewClient(ctx, *project, option.WithTokenSource(ts))
	if err != nil {
		sklog.Fatal(err)
	}
	topic := client.Topic("cloud-builds")
	hostname, err := os.Hostname()
	if err != nil {
		sklog.Fatal(err)
	}
	subName := fmt.Sprintf("continuous-deploy-%s", hostname)
	sub := client.Subscription(subName)
	ok, err := sub.Exists(ctx)
	if err != nil {
		sklog.Fatalf("Failed checking subscription existence: %s", err)
	}
	if !ok {
		sub, err = client.CreateSubscription(ctx, subName, pubsub.SubscriptionConfig{
			Topic: topic,
		})
		if err != nil {
			sklog.Fatalf("Failed creating subscription: %s", err)
		}
	}
	pushk := "/usr/local/bin/pushk"
	if *local {
		pushk = "pushk"
	}
	shortImageNames := flag.Args()

	pubSubReceive := metrics2.NewLiveness("ci_pubsub_receive", nil)
	for {
		err := sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
			msg.Ack()
			sklog.Infof("Status: %s", msg.Attributes["status"])

			var buildInfo cloudbuild.Build
			if err := json.Unmarshal(msg.Data, &buildInfo); err != nil {
				sklog.Errorf("Failed to decode: %s: %q", err, string(msg.Data))
				return
			}

			// Record build failures so we can alert on them.
			if util.In(msg.Attributes["status"], []string{"FAILURE", "SUCCESS"}) {
				failure := 0
				if msg.Attributes["status"] == "FAILURE" {
					failure = 1
				}
				metrics2.GetInt64Metric("ci_build_failure", map[string]string{"trigger": buildInfo.Source.RepoSource.RepoName}).Update(int64(failure))
			}
			if msg.Attributes["status"] != "SUCCESS" {
				return
			}
			imageNames := imagesFromInfo(shortImageNames, buildInfo)
			if err != nil {
				sklog.Error(err)
				return
			}
			if len(imageNames) == 0 {
				sklog.Infof("No images to push.")
				return
			}
			cmd := fmt.Sprintf("%s --logtostderr %s", pushk, strings.Join(imageNames, " "))
			sklog.Infof("About to execute: %q", cmd)
			output, err := exec.RunSimple(ctx, cmd)
			pushFailure := metrics2.GetCounter("ci_push_failure", map[string]string{"trigger": buildInfo.Source.RepoSource.RepoName})
			if err != nil {
				sklog.Errorf("Failed to run pushk: %s: %s", output, err)
				pushFailure.Inc(1)
				return
			} else {
				sklog.Info(output)
			}
			pushFailure.Reset()
			pubSubReceive.Reset()
			sklog.Info("Finished push")
		})
		if err != nil {
			sklog.Errorf("Failed receiving pubsub message: %s", err)
		}
	}
}
<commit_msg>continuous-deploy - Fix failure when repo name isn't set.<commit_after>// continuous-deploy monitors pubsub events for the GCP Container Builder and
// pushes updated images when successful images are built.

package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"os"
	"regexp"
	"strings"

	"cloud.google.com/go/pubsub"
	"go.skia.org/infra/go/auth"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/exec"
	"go.skia.org/infra/go/gitauth"
	"go.skia.org/infra/go/metrics2"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/util"
	cloudbuild "google.golang.org/api/cloudbuild/v1"
	"google.golang.org/api/option"
)

// flags
var (
	local    = flag.Bool("local", false, "Running locally if true. As opposed to in production.")
	project  = flag.String("project", "skia-public", "The GCE project name.")
	promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
)

var (
	parseImageName *regexp.Regexp
)

func Init() {
	parseImageName = regexp.MustCompile("^gcr.io/" + *project + "/([^:]+).*$")
}

// baseImageName returns "fiddler" from "gcr.io/skia-public/fiddler:foo".
//
// If the image name doesn't start with "gcr.io" and the project name then "" is returned.
func baseImageName(s string) string {
	matches := parseImageName.FindStringSubmatch(s)
	if len(matches) != 2 {
		return ""
	} else {
		return matches[1]
	}
}

// imagesFromInfo parses the incoming PubSub Data 'b' as JSON and then returns
// the full image names of all the images that match 'shortImageNames'.
func imagesFromInfo(shortImageNames []string, buildInfo cloudbuild.Build) []string {
	imageNames := []string{}
	for _, im := range buildInfo.Results.Images {
		sklog.Infof("ImageName: %s", im.Name)
		// Is this one of the images we are pushing?
		for _, name := range shortImageNames {
			if baseImageName(im.Name) == name {
				imageNames = append(imageNames, im.Name)
				break
			}
		}
	}
	return imageNames
}

func main() {
	common.InitWithMust(
		"continuous-deploy",
		common.PrometheusOpt(promPort),
	)
	if len(flag.Args()) == 0 {
		sklog.Fatal("continuous-deploy must be passed in at least one package name to push.")
	}
	Init()
	sklog.Infof("Pushing to: %v", flag.Args())
	ctx := context.Background()
	ts, err := auth.NewDefaultTokenSource(*local, pubsub.ScopePubSub, "https://www.googleapis.com/auth/gerritcodereview")
	if err != nil {
		sklog.Fatal(err)
	}
	if !*local {
		_, err := gitauth.New(ts, "/tmp/git-cookie", true, "skia-continuous-deploy@skia-public.iam.gserviceaccount.com")
		if err != nil {
			sklog.Fatal(err)
		}
	}
	client, err := pubsub.NewClient(ctx, *project, option.WithTokenSource(ts))
	if err != nil {
		sklog.Fatal(err)
	}
	topic := client.Topic("cloud-builds")
	hostname, err := os.Hostname()
	if err != nil {
		sklog.Fatal(err)
	}
	subName := fmt.Sprintf("continuous-deploy-%s", hostname)
	sub := client.Subscription(subName)
	ok, err := sub.Exists(ctx)
	if err != nil {
		sklog.Fatalf("Failed checking subscription existence: %s", err)
	}
	if !ok {
		sub, err = client.CreateSubscription(ctx, subName, pubsub.SubscriptionConfig{
			Topic: topic,
		})
		if err != nil {
			sklog.Fatalf("Failed creating subscription: %s", err)
		}
	}
	pushk := "/usr/local/bin/pushk"
	if *local {
		pushk = "pushk"
	}
	shortImageNames := flag.Args()

	pubSubReceive := metrics2.NewLiveness("ci_pubsub_receive", nil)
	for {
		err := sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
			msg.Ack()
			sklog.Infof("Status: %s", msg.Attributes["status"])

			var buildInfo cloudbuild.Build
			if err := json.Unmarshal(msg.Data, &buildInfo); err != nil {
				sklog.Errorf("Failed to decode: %s: %q", err, string(msg.Data))
				return
			}
			repoName := "--unknown--"
			if buildInfo.Source != nil && buildInfo.Source.RepoSource != nil {
				repoName = buildInfo.Source.RepoSource.RepoName
			}

			// Record build failures so we can alert on them.
			if util.In(msg.Attributes["status"], []string{"FAILURE", "SUCCESS"}) {
				failure := 0
				if msg.Attributes["status"] == "FAILURE" {
					failure = 1
				}
				metrics2.GetInt64Metric("ci_build_failure", map[string]string{"trigger": repoName}).Update(int64(failure))
			}
			if msg.Attributes["status"] != "SUCCESS" {
				return
			}
			imageNames := imagesFromInfo(shortImageNames, buildInfo)
			if err != nil {
				sklog.Error(err)
				return
			}
			if len(imageNames) == 0 {
				sklog.Infof("No images to push.")
				return
			}
			cmd := fmt.Sprintf("%s --logtostderr %s", pushk, strings.Join(imageNames, " "))
			sklog.Infof("About to execute: %q", cmd)
			output, err := exec.RunSimple(ctx, cmd)
			pushFailure := metrics2.GetCounter("ci_push_failure", map[string]string{"trigger": repoName})
			if err != nil {
				sklog.Errorf("Failed to run pushk: %s: %s", output, err)
				pushFailure.Inc(1)
				return
			} else {
				sklog.Info(output)
			}
			pushFailure.Reset()
			pubSubReceive.Reset()
			sklog.Info("Finished push")
		})
		if err != nil {
			sklog.Errorf("Failed receiving pubsub message: %s", err)
		}
	}
}
<|endoftext|>


<commit_before>package operator

import (
	"github.com/phzfi/RIC/server/images"
	"github.com/phzfi/RIC/server/ops"
)

type token struct{}

type imageProcessor chan token

func makeImageProcessor(size int) (t imageProcessor) {
	t = make(imageProcessor, size)

	for i := 0; i < size; i++ {
		t <- token{}
	}

	return
}

func (p imageProcessor) MakeBlob(startBlob []byte, operations []ops.Operation) ([]byte, error) {
	p.borrow()
	defer p.giveBack()

	img := images.NewImage()
	defer img.Destroy()

	if startBlob != nil {
		img.FromBlob(startBlob)
	}

	for _, op := range operations {
		err := op.Apply(img)
		if err != nil {
			return nil, err
		}
	}

	return img.Blob(), nil
}

func (p imageProcessor) borrow() {
	<-p
}

func (p imageProcessor) giveBack() {
	p <- token{}
}
<commit_msg>made processor public<commit_after>package operator

import (
	"github.com/phzfi/RIC/server/images"
	"github.com/phzfi/RIC/server/ops"
)

type token struct{}

type ImageProcessor chan token

func MakeImageProcessor(size int) (t imageProcessor) {
	t = make(imageProcessor, size)

	for i := 0; i < size; i++ {
		t <- token{}
	}

	return
}

// Takes an image as a blob and applies the given operations to it
// startBlob can be nil, in which case operations should start with an image loading operation
func (p imageProcessor) MakeBlob(startBlob []byte, operations []ops.Operation) ([]byte, error) {
	p.borrow()
	defer p.giveBack()

	img := images.NewImage()
	defer img.Destroy()

	if startBlob != nil {
		img.FromBlob(startBlob)
	}

	for _, op := range operations {
		err := op.Apply(img)
		if err != nil {
			return nil, err
		}
	}

	return img.Blob(), nil
}

func (p imageProcessor) borrow() {
	<-p
}

func (p imageProcessor) giveBack() {
	p <- token{}
}
<|endoftext|>