<commit_before>package router

import (
	"fmt"

	"github.com/ellcrys/util"
	"github.com/hashicorp/consul/api"
	logging "github.com/op/go-logging"
)

// RouterDomain is the router's domain
var RouterDomain = util.Env("ROUTER_DOMAIN", "whatbay.co")

// Helper defines a structure for hooking up to the
// reverse proxy tool within the cluster. Current implementation
// is designed to add frontend and backend entries in consul which is a
// traefik backend
type Helper struct {
	client *api.Client
	l      *logging.Logger
}

// NewHelper creates a new router helper object. Returns error
// if unable to connector to consul
func NewHelper(l *logging.Logger) (*Helper, error) {
	cfg := api.DefaultConfig()
	cfg.Address = util.Env("CONSUL_ADDR", cfg.Address)
	client, err := api.NewClient(cfg)
	if err != nil {
		return nil, fmt.Errorf("failed to create client: %s", err)
	}
	_, err = client.Status().Leader()
	if err != nil {
		return nil, fmt.Errorf("failed to create client: %s", err)
	}
	return &Helper{
		client: client,
		l:      l,
	}, nil
}

// AddFrontend adds a frontend to receive traffic from public internet
func (h *Helper) AddFrontend(name string) error {
	var frontend = fmt.Sprintf("/traefik/frontends/%s", name)
	var backendName = fmt.Sprintf("%s_be", name)
	var keys = map[string]string{
		frontend + "/backend":          backendName,
		frontend + "/entrypoints/0":    "http",
		frontend + "/routes/main/rule": fmt.Sprintf("Host:%s.%s", name, RouterDomain),
	}

	kv := h.client.KV()
	var ops api.KVTxnOps
	for key, value := range keys {
		ops = append(ops, &api.KVTxnOp{
			Verb:  api.KVSet,
			Key:   key,
			Value: []byte(value),
		})
	}

	ok, _, _, err := kv.Txn(ops, nil)
	if err != nil {
		return fmt.Errorf("failed to add frontend: %s", err)
	}

	if ok {
		return nil
	}
	return fmt.Errorf("failed to add frontend")
}

// AddBackend adds a backend server. This method
// will overwrite any backend rule matching the name
// and include a server that is issued a random name.
func (h *Helper) AddBackend(name string) error {
	var backend = fmt.Sprintf("/traefik/backends/%s", name)
	var backendServer = fmt.Sprintf("%s/servers/%s_server", backend, name)
	var keys = map[string]string{
		backend + "/loadbalancer/method": "drr",
		backendServer + "/url":           "http://localhost:3000",
		backendServer + "/weight":        "10",
	}

	kv := h.client.KV()
	var ops api.KVTxnOps
	for key, value := range keys {
		ops = append(ops, &api.KVTxnOp{
			Verb:  api.KVSet,
			Key:   key,
			Value: []byte(value),
		})
	}

	ok, _, _, err := kv.Txn(ops, nil)
	if err != nil {
		return fmt.Errorf("failed to add backend: %s", err)
	}

	if ok {
		return nil
	}
	return fmt.Errorf("failed to add backend")
}
<commit_msg>debug: see what is returned<commit_after>package router

import (
	"fmt"

	"github.com/ellcrys/util"
	"github.com/hashicorp/consul/api"
	logging "github.com/op/go-logging"
)

// RouterDomain is the router's domain
var RouterDomain = util.Env("ROUTER_DOMAIN", "whatbay.co")

// Helper defines a structure for hooking up to the
// reverse proxy tool within the cluster. Current implementation
// is designed to add frontend and backend entries in consul which is a
// traefik backend
type Helper struct {
	client *api.Client
	l      *logging.Logger
}

// NewHelper creates a new router helper object. Returns error
// if unable to connector to consul
func NewHelper(l *logging.Logger) (*Helper, error) {
	cfg := api.DefaultConfig()
	cfg.Address = util.Env("CONSUL_ADDR", cfg.Address)
	client, err := api.NewClient(cfg)
	if err != nil {
		return nil, fmt.Errorf("failed to create client: %s", err)
	}
	_, err = client.Status().Leader()
	if err != nil {
		return nil, fmt.Errorf("failed to create client: %s", err)
	}
	return &Helper{
		client: client,
		l:      l,
	}, nil
}

// AddFrontend adds a frontend to receive traffic from public internet
func (h *Helper) AddFrontend(name string) error {
	var frontend = fmt.Sprintf("/traefik/frontends/%s", name)
	var backendName = fmt.Sprintf("%s", name)
	var keys = map[string]string{
		frontend + "/backend":          backendName,
		frontend + "/entrypoints/0":    "http",
		frontend + "/routes/main/rule": fmt.Sprintf("Host:%s.%s", name, RouterDomain),
	}

	fmt.Println("Keys To Add: ")
	util.Printify(keys)

	kv := h.client.KV()
	var ops api.KVTxnOps
	for key, value := range keys {
		ops = append(ops, &api.KVTxnOp{
			Verb:  api.KVSet,
			Key:   key,
			Value: []byte(value),
		})
	}

	ok, txResp, _, err := kv.Txn(ops, nil)
	if err != nil {
		return fmt.Errorf("failed to add frontend: %s", err)
	}
	fmt.Println("OK? ", ok)
	util.Printify(txResp)
	if ok {
		return nil
	}
	return fmt.Errorf("failed to add frontend")
}

// AddBackend adds a backend server. This method
// will overwrite any backend rule matching the name
// and include a server that is issued a random name.
func (h *Helper) AddBackend(name string) error {
	var backend = fmt.Sprintf("/traefik/backends/%s", name)
	var backendServer = fmt.Sprintf("%s/servers/%s_server", backend, name)
	var keys = map[string]string{
		backend + "/loadbalancer/method": "drr",
		backendServer + "/url":           "http://localhost:3000",
		backendServer + "/weight":        "10",
	}

	kv := h.client.KV()
	var ops api.KVTxnOps
	for key, value := range keys {
		ops = append(ops, &api.KVTxnOp{
			Verb:  api.KVSet,
			Key:   key,
			Value: []byte(value),
		})
	}

	ok, _, _, err := kv.Txn(ops, nil)
	if err != nil {
		return fmt.Errorf("failed to add backend: %s", err)
	}

	if ok {
		return nil
	}
	return fmt.Errorf("failed to add backend")
}
<|endoftext|>


<commit_before>package raven

import (
	"net/http"
	"os"
	"regexp"
	"strings"

	"github.com/yext/glog"
)

var (
	projectName string
	hostname    string
	re          *regexp.Regexp
)

func init() {
	hostname, _ = os.Hostname()
	if short := strings.Index(hostname, "."); short != -1 {
		hostname = hostname[:short]
	}
	re = regexp.MustCompile("[0-9]{2,}")
}

// CaptureErrors sets the name of the project so that when events are
// sent to sentry they are tagged as coming from the given
// project. It then sets up the connection to sentry and begins
// to send any errors recieved over comm to sentry.
// It panics if a client could be initialized.
func CaptureErrors(project, dsn string, comm <-chan glog.Event) {
	projectName = project
	client, err := NewClient(dsn)
	if err != nil {
		panic(err)
	}

	for e := range comm {
		if e.Severity == "ERROR" {
			client.Capture(fromGlogEvent(e))
		}
	}
}

func separateMessageAndIds(message string) (string, string) {
	msg := re.ReplaceAllString(message, "[ID]")
	numbers := re.FindAllString(message, -1)
	ids := strings.Join(numbers, " ")
	return msg, ids
}

// fromGlogEvent converts a glog.Event to the format expected by Sentry.
func fromGlogEvent(e glog.Event) *Event {
	message := string(e.Message)
	if square := strings.Index(message, "]"); square != -1 {
		message = message[square+1:]
	}

	fullMessage := message
	if line := strings.Index(message, "\n"); line != -1 {
		message = message[:line]
	}

	msg, ids := separateMessageAndIds(message)

	eve := &Event{
		Project:    projectName,
		Level:      e.Severity,
		Message:    msg,
		ServerName: hostname,
		Extra:      map[string]interface{}{"FullMessage": fullMessage},
		StackTrace: BuildStackTrace(e.StackTrace),
		Logger:     os.Args[0],
	}

	data := map[string]interface{}{}
	for _, d := range e.Data {
		switch t := d.(type) {
		case *http.Request:
			eve.Http = NewHttp(t)
		case map[string]interface{}:
			for k, v := range t {
				data[k] = v
			}
		default:
			//TODO(ltacon): ignore for now...
		}
	}

	eve.Extra["Data"] = data
	eve.Extra["Source"] = sourceFromStack(eve.StackTrace)
	if ids != "" {
		eve.Extra["IDs"] = ids
	}

	return eve
}

// sourceFromStack retrieves the function and line where the
// event was logged from in the format "file.Function:118".
func sourceFromStack(s StackTrace) string {
	if len(s.Frames) == 0 {
		return ""
	}

	f := s.Frames[0]
	return f.Function + ":" + f.LineNo
}
<commit_msg>CaptureErrors: Log an error to stdout if we fail to send the error to Sentry<commit_after>package raven

import (
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"

	"github.com/yext/glog"
)

var (
	projectName string
	hostname    string
	re          *regexp.Regexp
)

func init() {
	hostname, _ = os.Hostname()
	if short := strings.Index(hostname, "."); short != -1 {
		hostname = hostname[:short]
	}
	re = regexp.MustCompile("[0-9]{2,}")
}

// CaptureErrors sets the name of the project so that when events are
// sent to sentry they are tagged as coming from the given
// project. It then sets up the connection to sentry and begins
// to send any errors recieved over comm to sentry.
// It panics if a client could be initialized.
func CaptureErrors(project, dsn string, comm <-chan glog.Event) {
	projectName = project
	client, err := NewClient(dsn)
	if err != nil {
		panic(err)
	}

	for e := range comm {
		if e.Severity == "ERROR" {
			if err := client.Capture(fromGlogEvent(e)); err != nil {
				// Don't use glog, or we'll just end up in an infinite loop
				log.Printf("An error occurred while attempting to send an error to Sentry:\n%v", err)
			}
		}
	}
}

func separateMessageAndIds(message string) (string, string) {
	msg := re.ReplaceAllString(message, "[ID]")
	numbers := re.FindAllString(message, -1)
	ids := strings.Join(numbers, " ")
	return msg, ids
}

// fromGlogEvent converts a glog.Event to the format expected by Sentry.
func fromGlogEvent(e glog.Event) *Event {
	message := string(e.Message)
	if square := strings.Index(message, "]"); square != -1 {
		message = message[square+1:]
	}

	fullMessage := message
	if line := strings.Index(message, "\n"); line != -1 {
		message = message[:line]
	}

	msg, ids := separateMessageAndIds(message)

	eve := &Event{
		Project:    projectName,
		Level:      e.Severity,
		Message:    msg,
		ServerName: hostname,
		Extra:      map[string]interface{}{"FullMessage": fullMessage},
		StackTrace: BuildStackTrace(e.StackTrace),
		Logger:     os.Args[0],
	}

	data := map[string]interface{}{}
	for _, d := range e.Data {
		switch t := d.(type) {
		case *http.Request:
			eve.Http = NewHttp(t)
		case map[string]interface{}:
			for k, v := range t {
				data[k] = v
			}
		default:
			//TODO(ltacon): ignore for now...
		}
	}

	eve.Extra["Data"] = data
	eve.Extra["Source"] = sourceFromStack(eve.StackTrace)
	if ids != "" {
		eve.Extra["IDs"] = ids
	}

	return eve
}

// sourceFromStack retrieves the function and line where the
// event was logged from in the format "file.Function:118".
func sourceFromStack(s StackTrace) string {
	if len(s.Frames) == 0 {
		return ""
	}

	f := s.Frames[0]
	return f.Function + ":" + f.LineNo
}
<|endoftext|>


<commit_before>package oas

import (
	"strings"

	"github.com/TykTechnologies/tyk/apidef"
	"github.com/getkin/kin-openapi/openapi3"
)

type Operations map[string]*Operation

type Operation struct {
	Allow                *Allowance `bson:"allow,omitempty" json:"allow,omitempty"`
	Block                *Allowance `bson:"block,omitempty" json:"block,omitempty"`
	IgnoreAuthentication *Allowance `bson:"ignoreAuthentication,omitempty" json:"ignoreAuthentication,omitempty"`
	// TransformRequestMethod allows you to transform the method of a request.
	TransformRequestMethod *TransformRequestMethod `bson:"transformRequestMethod,omitempty" json:"transformRequestMethod,omitempty"`
}

const (
	allow                AllowanceType = 0
	block                AllowanceType = 1
	ignoreAuthentication AllowanceType = 2
)

type AllowanceType int

func (s *OAS) fillPathsAndOperations(ep apidef.ExtendedPathsSet) {
	if s.Paths == nil {
		s.Paths = make(openapi3.Paths)
	}

	s.fillAllowance(ep.WhiteList, allow)
	s.fillAllowance(ep.BlackList, block)
	s.fillAllowance(ep.Ignored, ignoreAuthentication)
	s.fillTransformRequestMethod(ep.MethodTransforms)

	if len(s.Paths) == 0 {
		s.Paths = nil
	}
}

func (s *OAS) extractPathsAndOperations(ep *apidef.ExtendedPathsSet) {
	tykOperations := s.getTykOperations()
	if len(tykOperations) == 0 {
		return
	}

	for id, tykOp := range tykOperations {
	found:
		for path, pathItem := range s.Paths {
			for method, operation := range pathItem.Operations() {
				if id == operation.OperationID {
					tykOp.extractAllowanceTo(ep, path, method, allow)
					tykOp.extractAllowanceTo(ep, path, method, block)
					tykOp.extractAllowanceTo(ep, path, method, ignoreAuthentication)
					tykOp.extractTransformRequestMethodTo(ep, path, method)
					break found
				}
			}
		}
	}
}

func (s *OAS) fillAllowance(endpointMetas []apidef.EndPointMeta, typ AllowanceType) {
	for _, em := range endpointMetas {
		operationID := s.getOperationID(em.Path, em.Method)
		operation := s.GetTykExtension().getOperation(operationID)

		var allowance *Allowance

		switch typ {
		case block:
			allowance = newAllowance(&operation.Block)
		case ignoreAuthentication:
			allowance = newAllowance(&operation.IgnoreAuthentication)
		default:
			allowance = newAllowance(&operation.Allow)
		}

		allowance.Fill(em)
		if ShouldOmit(allowance) {
			allowance = nil
		}
	}
}

func newAllowance(prev **Allowance) *Allowance {
	if *prev == nil {
		*prev = &Allowance{}
	}

	return *prev
}

func (s *OAS) fillTransformRequestMethod(metas []apidef.MethodTransformMeta) {
	for _, meta := range metas {
		operationID := s.getOperationID(meta.Path, meta.Method)
		operation := s.GetTykExtension().getOperation(operationID)
		if operation.TransformRequestMethod == nil {
			operation.TransformRequestMethod = &TransformRequestMethod{}
		}

		operation.TransformRequestMethod.Fill(meta)
		if ShouldOmit(operation.TransformRequestMethod) {
			operation.TransformRequestMethod = nil
		}
	}
}

func (o *Operation) extractAllowanceTo(ep *apidef.ExtendedPathsSet, path string, method string, typ AllowanceType) {
	allowance := o.Allow
	endpointMetas := &ep.WhiteList

	switch typ {
	case block:
		allowance = o.Block
		endpointMetas = &ep.BlackList
	case ignoreAuthentication:
		allowance = o.IgnoreAuthentication
		endpointMetas = &ep.Ignored
	}

	if allowance == nil {
		return
	}

	endpointMeta := apidef.EndPointMeta{Path: path, Method: method}
	allowance.ExtractTo(&endpointMeta)
	*endpointMetas = append(*endpointMetas, endpointMeta)
}

func (o *Operation) extractTransformRequestMethodTo(ep *apidef.ExtendedPathsSet, path string, method string) {
	if o.TransformRequestMethod == nil {
		return
	}

	meta := apidef.MethodTransformMeta{Path: path, Method: method}
	o.TransformRequestMethod.ExtractTo(&meta)
	ep.MethodTransforms = append(ep.MethodTransforms, meta)
}

func (s *OAS) getOperationID(path, method string) (operationID string) {
	operationID = strings.TrimPrefix(path, "/") + method

	if s.Paths[path] == nil {
		s.Paths[path] = &openapi3.PathItem{}
	}

	p := s.Paths[path]
	operation := p.GetOperation(method)
	if operation == nil {
		p.SetOperation(method, &openapi3.Operation{OperationID: operationID})
		return operationID
	}

	if operation.OperationID == "" {
		operation.OperationID = operationID
	} else {
		operationID = operation.OperationID
	}

	return
}

func (x *XTykAPIGateway) getOperation(operationID string) *Operation {
	if x.Middleware == nil {
		x.Middleware = &Middleware{}
	}

	middleware := x.Middleware

	if middleware.Operations == nil {
		middleware.Operations = make(Operations)
	}

	operations := middleware.Operations

	if operations[operationID] == nil {
		operations[operationID] = &Operation{}
	}

	return operations[operationID]
}
<commit_msg>Add Cache operation to OAS (#3942)<commit_after>package oas

import (
	"strings"

	"github.com/TykTechnologies/tyk/apidef"
	"github.com/getkin/kin-openapi/openapi3"
)

type Operations map[string]*Operation

type Operation struct {
	Allow                *Allowance `bson:"allow,omitempty" json:"allow,omitempty"`
	Block                *Allowance `bson:"block,omitempty" json:"block,omitempty"`
	IgnoreAuthentication *Allowance `bson:"ignoreAuthentication,omitempty" json:"ignoreAuthentication,omitempty"`
	// TransformRequestMethod allows you to transform the method of a request.
	TransformRequestMethod *TransformRequestMethod `bson:"transformRequestMethod,omitempty" json:"transformRequestMethod,omitempty"`
	Cache                  *CachePlugin            `bson:"cache,omitempty" json:"cache,omitempty"`
}

const (
	allow                AllowanceType = 0
	block                AllowanceType = 1
	ignoreAuthentication AllowanceType = 2
)

type AllowanceType int

func (s *OAS) fillPathsAndOperations(ep apidef.ExtendedPathsSet) {
	if s.Paths == nil {
		s.Paths = make(openapi3.Paths)
	}

	s.fillAllowance(ep.WhiteList, allow)
	s.fillAllowance(ep.BlackList, block)
	s.fillAllowance(ep.Ignored, ignoreAuthentication)
	s.fillTransformRequestMethod(ep.MethodTransforms)
	s.fillCache(ep.AdvanceCacheConfig)

	if len(s.Paths) == 0 {
		s.Paths = nil
	}
}

func (s *OAS) extractPathsAndOperations(ep *apidef.ExtendedPathsSet) {
	tykOperations := s.getTykOperations()
	if len(tykOperations) == 0 {
		return
	}

	for id, tykOp := range tykOperations {
	found:
		for path, pathItem := range s.Paths {
			for method, operation := range pathItem.Operations() {
				if id == operation.OperationID {
					tykOp.extractAllowanceTo(ep, path, method, allow)
					tykOp.extractAllowanceTo(ep, path, method, block)
					tykOp.extractAllowanceTo(ep, path, method, ignoreAuthentication)
					tykOp.extractTransformRequestMethodTo(ep, path, method)
					tykOp.extractCacheTo(ep, path, method)
					break found
				}
			}
		}
	}
}

func (s *OAS) fillAllowance(endpointMetas []apidef.EndPointMeta, typ AllowanceType) {
	for _, em := range endpointMetas {
		operationID := s.getOperationID(em.Path, em.Method)
		operation := s.GetTykExtension().getOperation(operationID)

		var allowance *Allowance

		switch typ {
		case block:
			allowance = newAllowance(&operation.Block)
		case ignoreAuthentication:
			allowance = newAllowance(&operation.IgnoreAuthentication)
		default:
			allowance = newAllowance(&operation.Allow)
		}

		allowance.Fill(em)
		if ShouldOmit(allowance) {
			allowance = nil
		}
	}
}

func newAllowance(prev **Allowance) *Allowance {
	if *prev == nil {
		*prev = &Allowance{}
	}

	return *prev
}

func (s *OAS) fillTransformRequestMethod(metas []apidef.MethodTransformMeta) {
	for _, meta := range metas {
		operationID := s.getOperationID(meta.Path, meta.Method)
		operation := s.GetTykExtension().getOperation(operationID)
		if operation.TransformRequestMethod == nil {
			operation.TransformRequestMethod = &TransformRequestMethod{}
		}

		operation.TransformRequestMethod.Fill(meta)
		if ShouldOmit(operation.TransformRequestMethod) {
			operation.TransformRequestMethod = nil
		}
	}
}

func (s *OAS) fillCache(metas []apidef.CacheMeta) {
	for _, meta := range metas {
		operationID := s.getOperationID(meta.Path, meta.Method)
		operation := s.GetTykExtension().getOperation(operationID)
		if operation.Cache == nil {
			operation.Cache = &CachePlugin{}
		}

		operation.Cache.Fill(meta)
		if ShouldOmit(operation.Cache) {
			operation.Cache = nil
		}
	}
}

func (o *Operation) extractAllowanceTo(ep *apidef.ExtendedPathsSet, path string, method string, typ AllowanceType) {
	allowance := o.Allow
	endpointMetas := &ep.WhiteList

	switch typ {
	case block:
		allowance = o.Block
		endpointMetas = &ep.BlackList
	case ignoreAuthentication:
		allowance = o.IgnoreAuthentication
		endpointMetas = &ep.Ignored
	}

	if allowance == nil {
		return
	}

	endpointMeta := apidef.EndPointMeta{Path: path, Method: method}
	allowance.ExtractTo(&endpointMeta)
	*endpointMetas = append(*endpointMetas, endpointMeta)
}

func (o *Operation) extractTransformRequestMethodTo(ep *apidef.ExtendedPathsSet, path string, method string) {
	if o.TransformRequestMethod == nil {
		return
	}

	meta := apidef.MethodTransformMeta{Path: path, Method: method}
	o.TransformRequestMethod.ExtractTo(&meta)
	ep.MethodTransforms = append(ep.MethodTransforms, meta)
}

func (o *Operation) extractCacheTo(ep *apidef.ExtendedPathsSet, path string, method string) {
	if o.Cache == nil {
		return
	}

	newCacheMeta := apidef.CacheMeta{
		Method: method,
		Path:   path,
	}
	o.Cache.ExtractTo(&newCacheMeta)
	ep.AdvanceCacheConfig = append(ep.AdvanceCacheConfig, newCacheMeta)
}

func (s *OAS) getOperationID(path, method string) (operationID string) {
	operationID = strings.TrimPrefix(path, "/") + method

	if s.Paths[path] == nil {
		s.Paths[path] = &openapi3.PathItem{}
	}

	p := s.Paths[path]
	operation := p.GetOperation(method)
	if operation == nil {
		p.SetOperation(method, &openapi3.Operation{OperationID: operationID})
		return operationID
	}

	if operation.OperationID == "" {
		operation.OperationID = operationID
	} else {
		operationID = operation.OperationID
	}

	return
}

func (x *XTykAPIGateway) getOperation(operationID string) *Operation {
	if x.Middleware == nil {
		x.Middleware = &Middleware{}
	}

	middleware := x.Middleware

	if middleware.Operations == nil {
		middleware.Operations = make(Operations)
	}

	operations := middleware.Operations

	if operations[operationID] == nil {
		operations[operationID] = &Operation{}
	}

	return operations[operationID]
}
<|endoftext|>


<commit_before>/* vim: set sw=4 sts=4 et foldmethod=syntax : */

/*
 * Copyright (c) 2011 Alexander Færøy <ahf@0x90.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
    "bufio"
    "fmt"
    "net"
    "strconv"
    "strings"
    "time"
)

type Client struct {
    input chan *Message // Parsed IRC messages.
    output chan string // Output.

    output_quit chan bool // Leave the Output Handler.
    protocol_handler_quit chan bool // Leave the (Un)registered protocol handler.

    nickname string // The Nickname of the client.
    username string // The Username of the client.
    realname string // The Realname/Gecos of the client.
    hostname string // The hostname of the client (after we have done a reverse DNS lookup).

    address string // The IP address of the client.
    port int // The port.

    connection net.Conn // The connection.
    readwriter *bufio.ReadWriter // The buffered reader/writer.
    listener Listener // The listener.

    ircd *Ircd // Pointer to our IRCd.

    channels *ChannelSet // Channels.

    timestamp int64 // Creation time in seconds since UNIX epoch.

    // Housekeeping (FIXME: Needs refactoring).
    isRegistered bool
}

func NewClient(listener Listener, ircd *Ircd, connection net.Conn, remoteAddr string) {
    client := new(Client)

    // The IRCd.
    client.ircd = ircd

    // Timestamp.
    client.timestamp = time.Seconds()

    // IRC Channels.
    client.channels = NewChannelSet()

    // Channels.
    client.input = make(chan *Message)
    client.output = make(chan string)

    // Quit Channels.  These are being used to signal to each of our client
    // processes that it's time to exit.
    client.output_quit = make(chan bool)
    client.protocol_handler_quit = make(chan bool)

    // RemoteAddr is on the form "IP:Port". Split it.
    address, portStr, error := net.SplitHostPort(remoteAddr)

    if error != nil {
        panic("Bug: Unable to split host/port.")
    }

    // Convert it to an integer.
    port, error := strconv.Atoi(portStr)

    if error != nil {
        panic("Bug: Unable to convert port to an int.")
    }

    // Set address and port.
    client.address = address
    client.port = port

    // Start background job for looking up the hostname.
    hostnameCh := LookupAddress(address)

    // Set initial values.
    client.nickname = "*"
    client.username = "*"
    client.realname = "*"

    // Use the IP for now.
    client.hostname = address

    // Set connection, listener and create readwriter.
    client.connection = connection
    client.listener = listener
    client.readwriter = bufio.NewReadWriter(bufio.NewReader(connection), bufio.NewWriter(connection))

    // Used for synchronisation once we are done looking up rDNS and do identd
    // check.
    sync := make(chan bool)

    // Socket Output Handler.
    go client.OutputHandler()

    // Socket Input Handler.
    go client.InputHandler()

    // Unregistered Protocol Handler.  The Unregistered Protocol Handler only
    // accepts either the NICK or USER command. Once the client has been
    // registered, the UnregisteredProtocolHandler() will die and the
    // RegisteredProtocolHandler() will take over.
    go client.UnregisteredProtocolHandler(sync)

    // Start sending IRC messages to the client.
    client.WriteStringF("NOTICE AUTH :*** Processing connection to %s ...", ircd.Me())
    client.WriteString("NOTICE AUTH :*** Looking up your hostname ...")

    // Get result from rDNS lookup.
    host := <-hostnameCh
    client.WriteStringF("NOTICE AUTH :*** Found your hostname (%s) ...", host)
    client.hostname = host

    // Let the UnregisteredProtocolHandler() know that it's okay to exit and
    // let the RegisteredProtocolHandler() take over now.
    sync<-true
}

func (this *Client) InputHandler() {
    this.Printf("Entering Socket Input Handler")
    defer this.Printf("Leaving Socket Input Handler")

    rw := this.readwriter

    for {
        // Read until a newline.
        s, error := rw.ReadString('\n')

        if error != nil {
            // Read Error (Usually happens when the connection is closed by the
            // client).
            // FIXME: Should probably check for non-EOF errors.
            this.Printf("Read Error: %s", error)

            // Start a chain of Quit-messages to the various client processes
            // to ensure that they are all shutdown gracefully. The goal of
            // this is to avoid any dangling processes.
            this.output_quit<-true

            // Exit.
            return
        }

        // Trim "\r\n".
        line := strings.TrimRight(s, "\r\n")

        // Skip "empty" lines.
        if len(Strip(line)) == 0 {
            continue
        }

        // Parse.
        message := Parse(line)

        // Most ASCII strings are valid IRC commands, but in the
        // unlikely case that Parse() returns nil, we'll simply skip
        // it.
        if message != nil {
            this.Printf("-> '%s'", message)

            // Pass on to the ProtocolHandler (Either Registered or Unregistered).
            this.input<-message
        }
    }
}

func (this *Client) OutputHandler() {
    this.Printf("Entering Socket Output Handler")
    defer this.Printf("Leaving Socket Output Handler")

    defer func () {
        this.Printf("Sending Quit-message to the Protocol Handler")
        this.protocol_handler_quit<-true
    }()

    rw := this.readwriter

    for {
        select {
            case <-this.output_quit:
                // We were told to exit. Notice the above defer statements
                // where we chain the quit message on to the message handler
                // process.
                return

            case s, ok := <-this.output:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Ignore the byte-count for now.  Go sets error to non-nil if
                // bytes != len(input).
                _, error := rw.WriteString(s + "\r\n")

                if error != nil {
                    this.Printf("Write Error: %s", error)

                    // Unlikely, but we'll quit.
                    return
                }

                this.Printf("<- '%s'", s)

                // Flush the buffer.
                rw.Flush()
        }
    }
}

func (this *Client) UnregisteredProtocolHandler(sync chan bool) {
    this.Printf("Entering Unregistered Client Protocol Handler")
    defer this.Printf("Leaving Unregistered Client Protocol Handler")

    // The IRCd.
    ircd := this.ircd

    // Housekeeping.
    got_nickname := false
    got_username := false

    // Temporaries.
    var nickname string
    var username string
    var realname string

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // This client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                cmd := message.Command()
                args := message.Arguments()

                switch cmd {
                    case "NICK":
                        // Missing nickname.
                        if len(args) < 1 {
                            this.SendNumeric(ERR_NONICKNAMEGIVEN, ircd.Me(), "*")
                            continue
                        }

                        n := args[0]

                        // Check if the nickname is valid.
                        if ! IsValidNickname(n) {
                            this.SendNumeric(ERR_ERRONEUSNICKNAME, ircd.Me(), "*", n)
                            continue
                        }

                        // Check if the nickname is already registered.
                        if this.ircd.FindClient(n) != nil {
                            this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", n)
                            continue
                        }

                        got_nickname = true
                        nickname = n

                    case "USER":
                        // Missing parameters.
                        if len(args) < 4 {
                            this.SendNumeric(ERR_NEEDMOREPARAMS, ircd.Me(), "*", cmd)
                            continue
                        }

                        u := args[0]
                        r := args[3]

                        // Check if the username is valid.
                        if ! IsValidUsername(u) {
                            // FIXME: Handle ...
                        }

                        got_username = true

                        // FIXME: We Check Identd later.
                        username = "~" + u
                        realname = r
                }

                if got_nickname && got_username {
                    // Update the client information.  NOTE: It's important to
                    // do this *before* any attempts to register using
                    // this.Register() since otherwise it'll try to register
                    // using the "default" nickname, which is "*", and weird
                    // things may or may not happen.
                    this.nickname = nickname
                    this.username = username
                    this.realname = realname

                    // Try doing a registration.
                    // Check if the nickname has already been registered, just
                    // in case someone else connected whilst we were connecting
                    // and stole our nickname.
                    if ! this.Register() {
                        this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", nickname)
                        continue
                    }

                    // Wait for the synchronisation signal.  We have to do rDNS
                    // lookup and identd check before we proceed to the
                    // registered protocol handler.
                    this.Printf("Waiting for synchronisation")
                    <-sync
                    this.Printf("Synchronisation done")

                    // Let the full IRC protocol handler take over now.
                    go this.RegisteredProtocolHandler()
                    return
                }

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) RegisteredProtocolHandler() {
    this.Printf("Entering Registered Client Protocol Handler!")
    defer this.Printf("Leaving Registered Client Protocol Handler!")

    // The IRCd.
    ircd := this.ircd

    // The server information string.
    server := fmt.Sprintf("%s[%s]", ircd.Me(), this.listener.Address())

    // Welcome the client.
    this.SendNumeric(RPL_WELCOME, ircd.Me(), this.Nick(), ircd.Description(), this.Nick())
    this.SendNumeric(RPL_YOURHOST, ircd.Me(), this.Nick(), server, VersionFull)

    // Send MOTD, if any.
    this.SendMotd()

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Handle the message.
                HandleMessage(this, message)

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) WriteString(s string) {
    this.output<-s
}

func (this *Client) WriteStringF(format string, a...interface{}) {
    this.WriteString(fmt.Sprintf(format, a...))
}

func (this *Client) SendNumeric(n Numeric, a...interface{}) {
    this.WriteString(Format(n, a...))
}

func (this *Client) Unregister() {
    this.Printf("Unregistering client")

    if this.isRegistered {
        this.ircd.UnregisterClient(this)
    }
}

func (this *Client) Register() bool {
    this.Printf("Trying to register client")

    if this.isRegistered {
        panic("Bug: Trying to register an already registered client.")
    }

    b := this.ircd.RegisterClient(this)

    this.isRegistered = b
    return b
}

func (this *Client) SendMotd() {
    ircd := this.ircd
    motd := ircd.Motd()

    if motd == nil {
        this.SendNumeric(ERR_NOMOTD, ircd.Me(), this.Nick())
        return
    }

    lines := *motd

    this.SendNumeric(RPL_MOTDSTART, ircd.Me(), this.Nick(), ircd.Me())

    for i := range lines {
        this.SendNumeric(RPL_MOTD, ircd.Me(), this.Nick(), lines[i])
    }

    this.SendNumeric(RPL_ENDOFMOTD, ircd.Me(), this.Nick())
}

func (this *Client) Quit(message string) {
    // FIXME: Hack: Should be fixed like the description in bug #13.
    // Send a PART for now.
    this.channels.ForEach(func (channel *Channel) {
        channel.Part(this)
    })

    this.WriteStringF(":%s QUIT :%s", this, message)

    // The Socket Input Handler will start the chain that kills the other
    // processes.
    this.Close()
}

func (this *Client) Pong(message string) {
    this.WriteStringF("PONG :%s", message)
}

func (this *Client) Close() {
    this.Printf("Closing")
    defer this.Printf("Finished Closing")

    // Unregister the client.
    this.Unregister()

    // Cleanup.
    this.connection.Close()
}

func (this *Client) PrivateMessage(message *PrivateMessage) {
    this.WriteString(message.String())
}

func (this *Client) Notice(message *NoticeMessage) {
    this.WriteString(message.String())
}

func (this *Client) Printf(format string, a...interface{}) {
    this.ircd.Printf(this.String() + ": " + format, a...)
}

func (this *Client) RemotePort() int {
    return this.port
}

func (this *Client) RemoteAddress() string {
    return this.address
}

func (this *Client) Nick() string {
    return this.nickname
}

func (this *Client) User() string {
    return this.username
}

func (this *Client) Host() string {
    return this.hostname
}

func (this *Client) Real() string {
    return this.realname
}

func (this *Client) Join(channel *Channel) {
    channel.Join(this)
    this.channels.Insert(channel)
}

func (this *Client) Part(channel *Channel) {
    channel.Part(this)
    this.channels.Delete(channel)
}

func (this *Client) ChannelJoin(source *Client, channel *Channel) {
    this.WriteStringF(":%s JOIN :%s", source, channel)
}

func (this *Client) ChannelPart(source *Client, channel *Channel) {
    this.WriteStringF(":%s PART :%s", source, channel)
}

// FIXME: Should die in a refactoring session. Only needed by the
// CommandRegistry.
func (this *Client) Ircd() *Ircd {
    return this.ircd
}

func (this *Client) Secure() bool {
    return this.listener.Secure() == Secure
}

func (this *Client) WebSocket() bool {
    return this.listener.Protocol() == WebSocket
}

func (this *Client) ChannelNames() []string {
    return this.channels.Names()
}

func (this *Client) String() string {
    return fmt.Sprintf("%s!%s@%s", this.Nick(), this.User(), this.Host())
}
<commit_msg>Let NewClient() handle RegisteredProtocolHandler() instead of returning.<commit_after>/* vim: set sw=4 sts=4 et foldmethod=syntax : */

/*
 * Copyright (c) 2011 Alexander Færøy <ahf@0x90.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
    "bufio"
    "fmt"
    "net"
    "strconv"
    "strings"
    "time"
)

type Client struct {
    input chan *Message // Parsed IRC messages.
    output chan string // Output.

    output_quit chan bool // Leave the Output Handler.
    protocol_handler_quit chan bool // Leave the (Un)registered protocol handler.

    nickname string // The Nickname of the client.
    username string // The Username of the client.
    realname string // The Realname/Gecos of the client.
    hostname string // The hostname of the client (after we have done a reverse DNS lookup).

    address string // The IP address of the client.
    port int // The port.

    connection net.Conn // The connection.
    readwriter *bufio.ReadWriter // The buffered reader/writer.
    listener Listener // The listener.

    ircd *Ircd // Pointer to our IRCd.

    channels *ChannelSet // Channels.

    timestamp int64 // Creation time in seconds since UNIX epoch.

    // Housekeeping (FIXME: Needs refactoring).
    isRegistered bool
}

func NewClient(listener Listener, ircd *Ircd, connection net.Conn, remoteAddr string) {
    client := new(Client)

    // The IRCd.
    client.ircd = ircd

    // Timestamp.
    client.timestamp = time.Seconds()

    // IRC Channels.
    client.channels = NewChannelSet()

    // Channels.
    client.input = make(chan *Message)
    client.output = make(chan string)

    // Quit Channels.  These are being used to signal to each of our client
    // processes that it's time to exit.
    client.output_quit = make(chan bool)
    client.protocol_handler_quit = make(chan bool)

    // RemoteAddr is on the form "IP:Port". Split it.
    address, portStr, error := net.SplitHostPort(remoteAddr)

    if error != nil {
        panic("Bug: Unable to split host/port.")
    }

    // Convert it to an integer.
    port, error := strconv.Atoi(portStr)

    if error != nil {
        panic("Bug: Unable to convert port to an int.")
    }

    // Set address and port.
    client.address = address
    client.port = port

    // Start background job for looking up the hostname.
    hostnameCh := LookupAddress(address)

    // Set initial values.
    client.nickname = "*"
    client.username = "*"
    client.realname = "*"

    // Use the IP for now.
    client.hostname = address

    // Set connection, listener and create readwriter.
    client.connection = connection
    client.listener = listener
    client.readwriter = bufio.NewReadWriter(bufio.NewReader(connection), bufio.NewWriter(connection))

    // Used for synchronisation once we are done looking up rDNS and do identd
    // check.
    sync := make(chan bool)

    // Socket Output Handler.
    go client.OutputHandler()

    // Socket Input Handler.
    go client.InputHandler()

    // Unregistered Protocol Handler.  The Unregistered Protocol Handler only
    // accepts either the NICK or USER command. Once the client has been
    // registered, the UnregisteredProtocolHandler() will die and the
    // RegisteredProtocolHandler() will take over.
    go client.UnregisteredProtocolHandler(sync)

    // Start sending IRC messages to the client.
    client.WriteStringF("NOTICE AUTH :*** Processing connection to %s ...", ircd.Me())
    client.WriteString("NOTICE AUTH :*** Looking up your hostname ...")

    // Get result from rDNS lookup.
    host := <-hostnameCh
    client.WriteStringF("NOTICE AUTH :*** Found your hostname (%s) ...", host)
    client.hostname = host

    // Let the UnregisteredProtocolHandler() know that it's okay to exit and
    // let the RegisteredProtocolHandler() take over now.
    sync<-true

    // Spawn the RegisteredProtocolHandler().  NOTE: This is not a Go-routine,
    // because we are purposely taking over the current Go-routine spawned by
    // the network listener.  For more information, please see:
    // https://github.com/ahf/ircd-novo/issues/17
    client.RegisteredProtocolHandler()
}

func (this *Client) InputHandler() {
    this.Printf("Entering Socket Input Handler")
    defer this.Printf("Leaving Socket Input Handler")

    rw := this.readwriter

    for {
        // Read until a newline.
        s, error := rw.ReadString('\n')

        if error != nil {
            // Read Error (Usually happens when the connection is closed by the
            // client).
            // FIXME: Should probably check for non-EOF errors.
            this.Printf("Read Error: %s", error)

            // Start a chain of Quit-messages to the various client processes
            // to ensure that they are all shutdown gracefully. The goal of
            // this is to avoid any dangling processes.
            this.output_quit<-true

            // Exit.
            return
        }

        // Trim "\r\n".
        line := strings.TrimRight(s, "\r\n")

        // Skip "empty" lines.
        if len(Strip(line)) == 0 {
            continue
        }

        // Parse.
        message := Parse(line)

        // Most ASCII strings are valid IRC commands, but in the
        // unlikely case that Parse() returns nil, we'll simply skip
        // it.
        if message != nil {
            this.Printf("-> '%s'", message)

            // Pass on to the ProtocolHandler (Either Registered or Unregistered).
            this.input<-message
        }
    }
}

func (this *Client) OutputHandler() {
    this.Printf("Entering Socket Output Handler")
    defer this.Printf("Leaving Socket Output Handler")

    defer func () {
        this.Printf("Sending Quit-message to the Protocol Handler")
        this.protocol_handler_quit<-true
    }()

    rw := this.readwriter

    for {
        select {
            case <-this.output_quit:
                // We were told to exit. Notice the above defer statements
                // where we chain the quit message on to the message handler
                // process.
                return

            case s, ok := <-this.output:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Ignore the byte-count for now.  Go sets error to non-nil if
                // bytes != len(input).
                _, error := rw.WriteString(s + "\r\n")

                if error != nil {
                    this.Printf("Write Error: %s", error)

                    // Unlikely, but we'll quit.
                    return
                }

                this.Printf("<- '%s'", s)

                // Flush the buffer.
                rw.Flush()
        }
    }
}

func (this *Client) UnregisteredProtocolHandler(sync chan bool) {
    this.Printf("Entering Unregistered Client Protocol Handler")
    defer this.Printf("Leaving Unregistered Client Protocol Handler")

    // The IRCd.
    ircd := this.ircd

    // Housekeeping.
    got_nickname := false
    got_username := false

    // Temporaries.
    var nickname string
    var username string
    var realname string

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // This client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                cmd := message.Command()
                args := message.Arguments()

                switch cmd {
                    case "NICK":
                        // Missing nickname.
                        if len(args) < 1 {
                            this.SendNumeric(ERR_NONICKNAMEGIVEN, ircd.Me(), "*")
                            continue
                        }

                        n := args[0]

                        // Check if the nickname is valid.
                        if ! IsValidNickname(n) {
                            this.SendNumeric(ERR_ERRONEUSNICKNAME, ircd.Me(), "*", n)
                            continue
                        }

                        // Check if the nickname is already registered.
                        if this.ircd.FindClient(n) != nil {
                            this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", n)
                            continue
                        }

                        got_nickname = true
                        nickname = n

                    case "USER":
                        // Missing parameters.
                        if len(args) < 4 {
                            this.SendNumeric(ERR_NEEDMOREPARAMS, ircd.Me(), "*", cmd)
                            continue
                        }

                        u := args[0]
                        r := args[3]

                        // Check if the username is valid.
                        if ! IsValidUsername(u) {
                            // FIXME: Handle ...
                        }

                        got_username = true

                        // FIXME: We Check Identd later.
                        username = "~" + u
                        realname = r
                }

                if got_nickname && got_username {
                    // Update the client information.  NOTE: It's important to
                    // do this *before* any attempts to register using
                    // this.Register() since otherwise it'll try to register
                    // using the "default" nickname, which is "*", and weird
                    // things may or may not happen.
                    this.nickname = nickname
                    this.username = username
                    this.realname = realname

                    // Try doing a registration.
                    // Check if the nickname has already been registered, just
                    // in case someone else connected whilst we were connecting
                    // and stole our nickname.
                    if ! this.Register() {
                        this.SendNumeric(ERR_NICKNAMEINUSE, ircd.Me(), "*", nickname)
                        continue
                    }

                    // Wait for the synchronisation signal.  We have to do rDNS
                    // lookup and identd check before we proceed to the
                    // registered protocol handler.
                    this.Printf("Waiting for synchronisation")
                    <-sync
                    this.Printf("Synchronisation done")

                    // Let the full IRC protocol handler take over now.
                    return
                }

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) RegisteredProtocolHandler() {
    this.Printf("Entering Registered Client Protocol Handler!")
    defer this.Printf("Leaving Registered Client Protocol Handler!")

    // The IRCd.
    ircd := this.ircd

    // The server information string.
    server := fmt.Sprintf("%s[%s]", ircd.Me(), this.listener.Address())

    // Welcome the client.
    this.SendNumeric(RPL_WELCOME, ircd.Me(), this.Nick(), ircd.Description(), this.Nick())
    this.SendNumeric(RPL_YOURHOST, ircd.Me(), this.Nick(), server, VersionFull)

    // Send MOTD, if any.
    this.SendMotd()

    for {
        select {
            case message, ok := <-this.input:
                if ! ok {
                    // The client is dead.
                    // FIXME: Shouldn't happen.
                    return
                }

                // Handle the message.
                HandleMessage(this, message)

            case <-this.protocol_handler_quit:
                // We were told to quit.
                return
        }
    }
}

func (this *Client) WriteString(s string) {
    this.output<-s
}

func (this *Client) WriteStringF(format string, a...interface{}) {
    this.WriteString(fmt.Sprintf(format, a...))
}

func (this *Client) SendNumeric(n Numeric, a...interface{}) {
    this.WriteString(Format(n, a...))
}

func (this *Client) Unregister() {
    this.Printf("Unregistering client")

    if this.isRegistered {
        this.ircd.UnregisterClient(this)
    }
}

func (this *Client) Register() bool {
    this.Printf("Trying to register client")

    if this.isRegistered {
        panic("Bug: Trying to register an already registered client.")
    }

    b := this.ircd.RegisterClient(this)

    this.isRegistered = b
    return b
}

func (this *Client) SendMotd() {
    ircd := this.ircd
    motd := ircd.Motd()

    if motd == nil {
        this.SendNumeric(ERR_NOMOTD, ircd.Me(), this.Nick())
        return
    }

    lines := *motd

    this.SendNumeric(RPL_MOTDSTART, ircd.Me(), this.Nick(), ircd.Me())

    for i := range lines {
        this.SendNumeric(RPL_MOTD, ircd.Me(), this.Nick(), lines[i])
    }

    this.SendNumeric(RPL_ENDOFMOTD, ircd.Me(), this.Nick())
}

func (this *Client) Quit(message string) {
    // FIXME: Hack: Should be fixed like the description in bug #13.
    // Send a PART for now.
    this.channels.ForEach(func (channel *Channel) {
        channel.Part(this)
    })

    this.WriteStringF(":%s QUIT :%s", this, message)

    // The Socket Input Handler will start the chain that kills the other
    // processes.
    this.Close()
}

func (this *Client) Pong(message string) {
    this.WriteStringF("PONG :%s", message)
}

func (this *Client) Close() {
    this.Printf("Closing")
    defer this.Printf("Finished Closing")

    // Unregister the client.
    this.Unregister()

    // Cleanup.
    this.connection.Close()
}

func (this *Client) PrivateMessage(message *PrivateMessage) {
    this.WriteString(message.String())
}

func (this *Client) Notice(message *NoticeMessage) {
    this.WriteString(message.String())
}

func (this *Client) Printf(format string, a...interface{}) {
    this.ircd.Printf(this.String() + ": " + format, a...)
}

func (this *Client) RemotePort() int {
    return this.port
}

func (this *Client) RemoteAddress() string {
    return this.address
}

func (this *Client) Nick() string {
    return this.nickname
}

func (this *Client) User() string {
    return this.username
}

func (this *Client) Host() string {
    return this.hostname
}

func (this *Client) Real() string {
    return this.realname
}

func (this *Client) Join(channel *Channel) {
    channel.Join(this)
    this.channels.Insert(channel)
}

func (this *Client) Part(channel *Channel) {
    channel.Part(this)
    this.channels.Delete(channel)
}

func (this *Client) ChannelJoin(source *Client, channel *Channel) {
    this.WriteStringF(":%s JOIN :%s", source, channel)
}

func (this *Client) ChannelPart(source *Client, channel *Channel) {
    this.WriteStringF(":%s PART :%s", source, channel)
}

// FIXME: Should die in a refactoring session. Only needed by the
// CommandRegistry.
func (this *Client) Ircd() *Ircd {
    return this.ircd
}

func (this *Client) Secure() bool {
    return this.listener.Secure() == Secure
}

func (this *Client) WebSocket() bool {
    return this.listener.Protocol() == WebSocket
}

func (this *Client) ChannelNames() []string {
    return this.channels.Names()
}

func (this *Client) String() string {
    return fmt.Sprintf("%s!%s@%s", this.Nick(), this.User(), this.Host())
}
<|endoftext|>


<commit_before>package receiver

import "time"

// Helper for fast calculate days count from 1970-01-01 in local timezone
// Not thread-safe!
type DaysFrom1970 struct {
	todayStartTimestamp uint32
	todayEndTimestamp   uint32
	todayDays           uint16
}

func (dd *DaysFrom1970) fromTimestamp(timestamp uint32) uint16 {
	t := time.Unix(int64(timestamp), 0)
	return uint16(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Unix() / 86400)
}

func (dd *DaysFrom1970) Timestamp(timestamp uint32) uint16 {
	if timestamp >= dd.todayStartTimestamp && timestamp <= dd.todayEndTimestamp {
		return dd.todayDays
	}

	return dd.fromTimestamp(timestamp)
}

func (dd *DaysFrom1970) TimestampWithNow(timestamp uint32, now uint32) uint16 {
	if timestamp < dd.todayStartTimestamp {
		return dd.fromTimestamp(timestamp)
	}

	// timestamp >= pp.todayStartTimestamp
	if timestamp <= dd.todayEndTimestamp {
		return dd.todayDays
	}

	// timestamp > dd.todayEndTimestamp
	// check now date
	if now > dd.todayEndTimestamp {
		// update "today" required
		d := time.Unix(int64(now), 0)
		dd.todayStartTimestamp = uint32(time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, time.Local).Unix())
		dd.todayEndTimestamp = uint32(time.Date(d.Year(), d.Month(), d.Day(), 23, 59, 59, 0, time.Local).Unix())
		dd.todayDays = dd.fromTimestamp(timestamp)
	}

	return dd.Timestamp(timestamp)
}
<commit_msg>fix bug in days calculator<commit_after>package receiver

import "time"

// Helper for fast calculate days count from 1970-01-01 in local timezone
// Not thread-safe!
type DaysFrom1970 struct {
	todayStartTimestamp uint32
	todayEndTimestamp   uint32
	todayDays           uint16
}

func (dd *DaysFrom1970) fromTimestamp(timestamp uint32) uint16 {
	t := time.Unix(int64(timestamp), 0)
	return uint16(time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Unix() / 86400)
}

func (dd *DaysFrom1970) Timestamp(timestamp uint32) uint16 {
	if timestamp >= dd.todayStartTimestamp && timestamp <= dd.todayEndTimestamp {
		return dd.todayDays
	}

	return dd.fromTimestamp(timestamp)
}

func (dd *DaysFrom1970) TimestampWithNow(timestamp uint32, now uint32) uint16 {
	if timestamp < dd.todayStartTimestamp {
		return dd.fromTimestamp(timestamp)
	}

	// timestamp >= pp.todayStartTimestamp
	if timestamp <= dd.todayEndTimestamp {
		return dd.todayDays
	}

	// timestamp > dd.todayEndTimestamp
	// check now date
	if now > dd.todayEndTimestamp {
		// update "today" required
		d := time.Unix(int64(now), 0)
		dd.todayStartTimestamp = uint32(time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, time.Local).Unix())
		dd.todayEndTimestamp = uint32(time.Date(d.Year(), d.Month(), d.Day(), 23, 59, 59, 0, time.Local).Unix())
		dd.todayDays = dd.fromTimestamp(now)
	}

	return dd.Timestamp(timestamp)
}
<|endoftext|>


<commit_before>package main

import (
	"archive/tar"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

func deploy(dockerHost string, application *Application, composeFilePath string) (string, error) {
	var err error

	compose := NewCompose(dockerHost, composeFilePath, application.ProjectName)

	fmt.Println("=====> Building ...")

	if err = compose.Build(); err != nil {
		return "", err
	}

	fmt.Println("=====> Pulling ...")

	if err = compose.Pull(); err != nil {
		return "", err
	}

	fmt.Println("=====> Deploying ...")

	if err = compose.Up(); err != nil {
		return "", err
	}

	webContainerId, err := compose.GetContainerId("web")

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

	return webContainerId, nil
}

func injectBuildArgs(application *Application, composeFile *ComposeFile, etcd *Etcd) error {
	userDirectoryKey := "/paus/users/" + application.Username

	if !etcd.HasKey(userDirectoryKey) {
		return nil
	}

	appDirectoryKey := userDirectoryKey + "/" + application.AppName

	if !etcd.HasKey(appDirectoryKey) {
		return nil
	}

	buildArgsKey := appDirectoryKey + "/build-args/"

	if !etcd.HasKey(buildArgsKey) {
		return nil
	}

	buildArgKeys, err := etcd.List(buildArgsKey, false)

	if err != nil {
		return err
	}

	buildArgs := map[string]string{}

	for _, key := range buildArgKeys {
		value, err := etcd.Get(key)

		if err != nil {
			return err
		}

		buildArgs[strings.Replace(key, buildArgsKey, "", 1)] = value
	}

	composeFile.InjectBuildArgs(buildArgs)

	return nil
}

func injectEnvironmentVariables(application *Application, composeFile *ComposeFile, etcd *Etcd) error {
	userDirectoryKey := "/paus/users/" + application.Username

	if !etcd.HasKey(userDirectoryKey) {
		return nil
	}

	appDirectoryKey := userDirectoryKey + "/" + application.AppName

	if !etcd.HasKey(appDirectoryKey) {
		return nil
	}

	envDirectoryKey := appDirectoryKey + "/envs/"

	if !etcd.HasKey(envDirectoryKey) {
		return nil
	}

	envKeys, err := etcd.List(envDirectoryKey, false)

	if err != nil {
		return err
	}

	environmentVariables := map[string]string{}

	for _, key := range envKeys {
		value, err := etcd.Get(key)

		if err != nil {
			return err
		}

		environmentVariables[strings.Replace(key, envDirectoryKey, "", 1)] = value
	}

	composeFile.InjectEnvironmentVariables(environmentVariables)

	return nil
}

func registerApplicationMetadata(application *Application, etcd *Etcd) error {
	userDirectoryKey := "/paus/users/" + application.Username

	if !etcd.HasKey(userDirectoryKey) {
		_ = etcd.Mkdir(userDirectoryKey)
	}

	appDirectoryKey := userDirectoryKey + "/" + application.AppName

	if !etcd.HasKey(appDirectoryKey) {
		_ = etcd.Mkdir(appDirectoryKey)
		_ = etcd.Mkdir(appDirectoryKey + "/envs")
		_ = etcd.Mkdir(appDirectoryKey + "/revisions")
	}

	if err := etcd.Set(appDirectoryKey+"/revisions/"+application.Revision, strconv.FormatInt(time.Now().Unix(), 10)); err != nil {
		return err
	}

	return nil
}

func registerVulcandInformation(application *Application, baseDomain string, webContainer *Container, etcd *Etcd) error {
	vulcandDirectoryKeyBase := "/vulcand"

	// {"Type": "http"}
	if err := etcd.Set(vulcandDirectoryKeyBase+"/backends/"+application.ProjectName+"/backend", "{\"Type\": \"http\"}"); err != nil {
		return err
	}

	// {"URL": "http://$web_container_host_ip:$web_container_port"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/backends/"+application.ProjectName+"/servers/"+webContainer.ContainerId,
		"{\"URL\": \"http://"+webContainer.HostIP()+":"+webContainer.HostPort()+"\"}",
	); err != nil {
		return err
	}

	// {"Type": "http", "BackendId": "$PROJECT_NAME", "Route": "Host(`$PROJECT_NAME.$BASE_DOMAIN`) && PathRegexp(`/`)"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/frontends/"+application.ProjectName+"/frontend",
		"{\"Type\": \"http\", \"BackendId\": \""+application.ProjectName+"\", \"Route\": \"Host(`"+application.ProjectName+"."+baseDomain+"`) && PathRegexp(`/`)\"}",
	); err != nil {
		return err
	}

	// {"Type": "http", "BackendId": "$PROJECT_NAME", "Route": "Host(`$USER_NAME.$BASE_DOMAIN`) && PathRegexp(`/`)"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/frontends/"+application.Username+"/frontend",
		"{\"Type\": \"http\", \"BackendId\": \""+application.ProjectName+"\", \"Route\": \"Host(`"+application.Username+"."+baseDomain+"`) && PathRegexp(`/`)\"}",
	); err != nil {
		return err
	}

	// {"Type": "http", "BackendId": "$PROJECT_NAME", "Route": "Host(`$APP_NAME.$BASE_DOMAIN`) && PathRegexp(`/`)"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/frontends/"+application.AppName+"/frontend",
		"{\"Type\": \"http\", \"BackendId\": \""+application.ProjectName+"\", \"Route\": \"Host(`"+application.AppName+"."+baseDomain+"`) && PathRegexp(`/`)\"}",
	); err != nil {
		return err
	}

	return nil
}

func removeUnpackedFiles(repositoryPath, newComposeFilePath string) error {
	files, err := ioutil.ReadDir(repositoryPath)

	if err != nil {
		return err
	}

	for _, file := range files {
		if filepath.Join(repositoryPath, file.Name()) != newComposeFilePath {
			if err = os.RemoveAll(filepath.Join(repositoryPath, file.Name())); err != nil {
				return err
			}
		}
	}

	return nil
}

func unpackReceivedFiles(repositoryDir, username, projectName string, stdin io.Reader) (string, error) {
	repositoryPath := filepath.Join(repositoryDir, username, projectName)

	if err := os.MkdirAll(repositoryPath, 0777); err != nil {
		return "", err
	}

	reader := tar.NewReader(stdin)

	for {
		header, err := reader.Next()

		if err == io.EOF {
			break
		}

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

		buffer := new(bytes.Buffer)
		outPath := filepath.Join(repositoryPath, header.Name)

		switch header.Typeflag {
		case tar.TypeDir:
			if _, err = os.Stat(outPath); err != nil {
				os.MkdirAll(outPath, 0755)
			}

		case tar.TypeReg, tar.TypeRegA:
			if _, err = io.Copy(buffer, reader); err != nil {
				return "", err
			}

			if err = ioutil.WriteFile(outPath, buffer.Bytes(), os.FileMode(header.Mode)); err != nil {
				return "", err
			}
		}
	}

	return repositoryPath, nil
}

func main() {
	config, err := LoadConfig()

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	application := ApplicationFromArgs(os.Args[1:])
	repositoryPath, err := unpackReceivedFiles(config.RepositoryDir, application.Username, application.ProjectName, os.Stdin)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = os.Chdir(repositoryPath); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	composeFilePath := filepath.Join(repositoryPath, "docker-compose.yml")

	if _, err := os.Stat(composeFilePath); err != nil {
		fmt.Fprintln(os.Stderr, "=====> docker-compose.yml was NOT found!")
		os.Exit(1)
	}

	fmt.Println("=====> docker-compose.yml was found")
	composeFile, err := NewComposeFile(composeFilePath)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	etcd, err := NewEtcd(config.EtcdEndpoint)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = injectBuildArgs(application, composeFile, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = injectEnvironmentVariables(application, composeFile, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	composeFile.RewritePortBindings()
	newComposeFilePath := filepath.Join(repositoryPath, "docker-compose-"+strconv.FormatInt(time.Now().Unix(), 10)+".yml")

	if err = composeFile.SaveAs(newComposeFilePath); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	webContainerId, err := deploy(config.DockerHost, application, newComposeFilePath)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Println("=====> Application container is launched.")

	webContainer, err := ContainerFromID(config.DockerHost, webContainerId)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Println("=====> Registering metadata ...")

	if err = registerApplicationMetadata(application, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = registerVulcandInformation(application, config.BaseDomain, webContainer, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	urlList := []string{
		"http://" + application.ProjectName + "." + config.BaseDomain,
		"http://" + application.Username + "." + config.BaseDomain,
		"http://" + application.AppName + "." + config.BaseDomain,
	}

	fmt.Println("=====> " + application.Repository + " was successfully deployed at:")

	for _, url := range urlList {
		fmt.Println("         " + url)
	}

	if err = removeUnpackedFiles(repositoryPath, newComposeFilePath); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
<commit_msg>Rename application directory key<commit_after>package main

import (
	"archive/tar"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

func deploy(dockerHost string, application *Application, composeFilePath string) (string, error) {
	var err error

	compose := NewCompose(dockerHost, composeFilePath, application.ProjectName)

	fmt.Println("=====> Building ...")

	if err = compose.Build(); err != nil {
		return "", err
	}

	fmt.Println("=====> Pulling ...")

	if err = compose.Pull(); err != nil {
		return "", err
	}

	fmt.Println("=====> Deploying ...")

	if err = compose.Up(); err != nil {
		return "", err
	}

	webContainerId, err := compose.GetContainerId("web")

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

	return webContainerId, nil
}

func injectBuildArgs(application *Application, composeFile *ComposeFile, etcd *Etcd) error {
	userDirectoryKey := "/paus/users/" + application.Username

	if !etcd.HasKey(userDirectoryKey) {
		return nil
	}

	appDirectoryKey := userDirectoryKey + "/apps/" + application.AppName

	if !etcd.HasKey(appDirectoryKey) {
		return nil
	}

	buildArgsKey := appDirectoryKey + "/build-args/"

	if !etcd.HasKey(buildArgsKey) {
		return nil
	}

	buildArgKeys, err := etcd.List(buildArgsKey, false)

	if err != nil {
		return err
	}

	buildArgs := map[string]string{}

	for _, key := range buildArgKeys {
		value, err := etcd.Get(key)

		if err != nil {
			return err
		}

		buildArgs[strings.Replace(key, buildArgsKey, "", 1)] = value
	}

	composeFile.InjectBuildArgs(buildArgs)

	return nil
}

func injectEnvironmentVariables(application *Application, composeFile *ComposeFile, etcd *Etcd) error {
	userDirectoryKey := "/paus/users/" + application.Username

	if !etcd.HasKey(userDirectoryKey) {
		return nil
	}

	appDirectoryKey := userDirectoryKey + "/apps/" + application.AppName

	if !etcd.HasKey(appDirectoryKey) {
		return nil
	}

	envDirectoryKey := appDirectoryKey + "/envs/"

	if !etcd.HasKey(envDirectoryKey) {
		return nil
	}

	envKeys, err := etcd.List(envDirectoryKey, false)

	if err != nil {
		return err
	}

	environmentVariables := map[string]string{}

	for _, key := range envKeys {
		value, err := etcd.Get(key)

		if err != nil {
			return err
		}

		environmentVariables[strings.Replace(key, envDirectoryKey, "", 1)] = value
	}

	composeFile.InjectEnvironmentVariables(environmentVariables)

	return nil
}

func registerApplicationMetadata(application *Application, etcd *Etcd) error {
	userDirectoryKey := "/paus/users/" + application.Username

	if !etcd.HasKey(userDirectoryKey) {
		_ = etcd.Mkdir(userDirectoryKey)
	}

	appDirectoryKey := userDirectoryKey + "/apps/" + application.AppName

	if !etcd.HasKey(appDirectoryKey) {
		_ = etcd.Mkdir(appDirectoryKey)
		_ = etcd.Mkdir(appDirectoryKey + "/envs")
		_ = etcd.Mkdir(appDirectoryKey + "/revisions")
	}

	if err := etcd.Set(appDirectoryKey+"/revisions/"+application.Revision, strconv.FormatInt(time.Now().Unix(), 10)); err != nil {
		return err
	}

	return nil
}

func registerVulcandInformation(application *Application, baseDomain string, webContainer *Container, etcd *Etcd) error {
	vulcandDirectoryKeyBase := "/vulcand"

	// {"Type": "http"}
	if err := etcd.Set(vulcandDirectoryKeyBase+"/backends/"+application.ProjectName+"/backend", "{\"Type\": \"http\"}"); err != nil {
		return err
	}

	// {"URL": "http://$web_container_host_ip:$web_container_port"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/backends/"+application.ProjectName+"/servers/"+webContainer.ContainerId,
		"{\"URL\": \"http://"+webContainer.HostIP()+":"+webContainer.HostPort()+"\"}",
	); err != nil {
		return err
	}

	// {"Type": "http", "BackendId": "$PROJECT_NAME", "Route": "Host(`$PROJECT_NAME.$BASE_DOMAIN`) && PathRegexp(`/`)"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/frontends/"+application.ProjectName+"/frontend",
		"{\"Type\": \"http\", \"BackendId\": \""+application.ProjectName+"\", \"Route\": \"Host(`"+application.ProjectName+"."+baseDomain+"`) && PathRegexp(`/`)\"}",
	); err != nil {
		return err
	}

	// {"Type": "http", "BackendId": "$PROJECT_NAME", "Route": "Host(`$USER_NAME.$BASE_DOMAIN`) && PathRegexp(`/`)"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/frontends/"+application.Username+"/frontend",
		"{\"Type\": \"http\", \"BackendId\": \""+application.ProjectName+"\", \"Route\": \"Host(`"+application.Username+"."+baseDomain+"`) && PathRegexp(`/`)\"}",
	); err != nil {
		return err
	}

	// {"Type": "http", "BackendId": "$PROJECT_NAME", "Route": "Host(`$APP_NAME.$BASE_DOMAIN`) && PathRegexp(`/`)"}
	if err := etcd.Set(
		vulcandDirectoryKeyBase+"/frontends/"+application.AppName+"/frontend",
		"{\"Type\": \"http\", \"BackendId\": \""+application.ProjectName+"\", \"Route\": \"Host(`"+application.AppName+"."+baseDomain+"`) && PathRegexp(`/`)\"}",
	); err != nil {
		return err
	}

	return nil
}

func removeUnpackedFiles(repositoryPath, newComposeFilePath string) error {
	files, err := ioutil.ReadDir(repositoryPath)

	if err != nil {
		return err
	}

	for _, file := range files {
		if filepath.Join(repositoryPath, file.Name()) != newComposeFilePath {
			if err = os.RemoveAll(filepath.Join(repositoryPath, file.Name())); err != nil {
				return err
			}
		}
	}

	return nil
}

func unpackReceivedFiles(repositoryDir, username, projectName string, stdin io.Reader) (string, error) {
	repositoryPath := filepath.Join(repositoryDir, username, projectName)

	if err := os.MkdirAll(repositoryPath, 0777); err != nil {
		return "", err
	}

	reader := tar.NewReader(stdin)

	for {
		header, err := reader.Next()

		if err == io.EOF {
			break
		}

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

		buffer := new(bytes.Buffer)
		outPath := filepath.Join(repositoryPath, header.Name)

		switch header.Typeflag {
		case tar.TypeDir:
			if _, err = os.Stat(outPath); err != nil {
				os.MkdirAll(outPath, 0755)
			}

		case tar.TypeReg, tar.TypeRegA:
			if _, err = io.Copy(buffer, reader); err != nil {
				return "", err
			}

			if err = ioutil.WriteFile(outPath, buffer.Bytes(), os.FileMode(header.Mode)); err != nil {
				return "", err
			}
		}
	}

	return repositoryPath, nil
}

func main() {
	config, err := LoadConfig()

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	application := ApplicationFromArgs(os.Args[1:])
	repositoryPath, err := unpackReceivedFiles(config.RepositoryDir, application.Username, application.ProjectName, os.Stdin)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = os.Chdir(repositoryPath); err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	composeFilePath := filepath.Join(repositoryPath, "docker-compose.yml")

	if _, err := os.Stat(composeFilePath); err != nil {
		fmt.Fprintln(os.Stderr, "=====> docker-compose.yml was NOT found!")
		os.Exit(1)
	}

	fmt.Println("=====> docker-compose.yml was found")
	composeFile, err := NewComposeFile(composeFilePath)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	etcd, err := NewEtcd(config.EtcdEndpoint)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = injectBuildArgs(application, composeFile, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = injectEnvironmentVariables(application, composeFile, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	composeFile.RewritePortBindings()
	newComposeFilePath := filepath.Join(repositoryPath, "docker-compose-"+strconv.FormatInt(time.Now().Unix(), 10)+".yml")

	if err = composeFile.SaveAs(newComposeFilePath); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	webContainerId, err := deploy(config.DockerHost, application, newComposeFilePath)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Println("=====> Application container is launched.")

	webContainer, err := ContainerFromID(config.DockerHost, webContainerId)

	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	fmt.Println("=====> Registering metadata ...")

	if err = registerApplicationMetadata(application, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	if err = registerVulcandInformation(application, config.BaseDomain, webContainer, etcd); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	urlList := []string{
		"http://" + application.ProjectName + "." + config.BaseDomain,
		"http://" + application.Username + "." + config.BaseDomain,
		"http://" + application.AppName + "." + config.BaseDomain,
	}

	fmt.Println("=====> " + application.Repository + " was successfully deployed at:")

	for _, url := range urlList {
		fmt.Println("         " + url)
	}

	if err = removeUnpackedFiles(repositoryPath, newComposeFilePath); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/spf13/cobra"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/peterh/liner"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/iam"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/tj/go-debug"
import "strconv"
import "strings"
import "fmt"
import "os"

func init() {
	initCmd := &cobra.Command{
		Use:   "init",
		Short: "initializes a config for your function",
		Run:   initPhage,
	}

	cmds = append(cmds, initCmd)
}

type prompt struct {
	text           string
	description    string
	def            string
	required       bool
	stringStore    **string
	stringSetStore *[]*string
	intStore       **int64
	funcStore      func(string)
	completer      func(string) []string
}

func newPrompt() *prompt {
	return new(prompt)
}

func (p *prompt) withCompleter(f liner.Completer) *prompt {
	p.completer = f
	return p
}

func (p *prompt) isRequired() *prompt {
	p.required = true
	return p
}

func (p *prompt) setDef(d string) *prompt {
	p.def = d
	return p
}

func (p *prompt) setText(t string) *prompt {
	p.text = t
	return p
}

func (p *prompt) setDescription(t string) *prompt {
	p.description = t
	return p
}

func (p *prompt) withString(s **string) *prompt {
	p.stringStore = s
	return p
}

func (p *prompt) withStringSet(s *[]*string) *prompt {
	p.stringSetStore = s
	return p
}

func (p *prompt) withInt(s **int64) *prompt {
	p.intStore = s
	return p
}

func (p *prompt) withFunc(s func(string)) *prompt {
	p.funcStore = s
	return p
}

// helps you build a config file
func initPhage(c *cobra.Command, _ []string) {
	var err error
	l := liner.NewLiner()
	defer l.Close()
	l.SetCtrlCAborts(true)
	fmt.Println(`
  HELLO AND WELCOME

  This command will help you set up your code for deployment to Lambda!

  But we need some information from you, like what you want to name
  your function and a few other things!

  Please answer the prompts as they appear below:
	`)

	//reqMsg := "Sorry, that field is required. Try again."

	// set this callback we can use to call all the stuff
	var realCompleter liner.Completer
	l.SetCompleter(func(line string) []string {
		if realCompleter != nil {
			return realCompleter(line)
		}
		return nil
	})

	iCfg := new(Config)
	iCfg.IamRole = new(IamRole)
	iCfg.Location = new(Location)
	prompts := getPrompts(iCfg)

	for _, cPrompt := range prompts {
		p := cPrompt
		text := p.text
		if p.def != "" {
			text += " [" + p.def + "]"
		}

		text += ": "

		realCompleter = nil
		if p.completer != nil {
			realCompleter = p.completer
		}

		if p.description != "" {
			fmt.Print(p.description)
		}

		if s, err := l.Prompt(text); err == nil {
			input := s
			hasInput := input != ""
			if p.stringStore != nil {
				if hasInput {
					*p.stringStore = &input
				} else {
					*p.stringStore = &p.def
				}

			} else if p.stringSetStore != nil {
				var splitMe string
				if hasInput {
					splitMe = input
				} else {
					splitMe = p.def
				}

				spl := strings.Split(splitMe, ",")
				pspl := make([]*string, len(spl))
				for i, v := range spl {
					// we need to set the value
					// in a variable local to this block
					// because the pointed-to value in
					// `v` will change on the next
					// loop iteration
					realVal := v
					pspl[i] = &realVal
				}

				*p.stringSetStore = pspl

			} else if p.intStore != nil {
				var tParse string
				if hasInput {
					tParse = input
				} else {
					tParse = p.def
				}

				i, _ := strconv.ParseInt(tParse, 10, 64)
				*p.intStore = &i
			} else if p.funcStore != nil {
				// just call the function, man
				p.funcStore(s)
			}
		} else if err == liner.ErrPromptAborted {
			fmt.Println("Aborted")
			return
		} else {
			fmt.Println("Error reading line: ", err)
			return
		}
	}

	// merge in any existing properties from the config object
	var wCfg *Config
	if cfg != nil {
		// if there's a config object, merge these two together
		cfg.merge(iCfg)
		wCfg = cfg
	} else {
		wCfg = iCfg
	}

	cfgFile, _ := c.Flags().GetString("config")
	err = wCfg.writeToFile(cfgFile)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Setup complete; saved config to %s", cfgFile)
}

// returns all the prompts needed for the `init` command
func getPrompts(cfg *Config) []*prompt {
	wd, _ := os.Getwd()
	st, _ := os.Stat(wd)

	iamRoles, roleMap := getIamRoles()

	return []*prompt{
		newPrompt().
			withString(&cfg.Name).
			isRequired().
			setText("Enter a project name").
			setDef(st.Name()),
		newPrompt().
			withString(&cfg.Description).
			setText("Enter a project description if you'd like").
			setDef(""),
		newPrompt().
			withString(&cfg.Archive).
			setText("Enter a archive name if you'd like").
			setDef(st.Name() + ".zip"),
		newPrompt().
			withString(&cfg.Runtime).
			isRequired().
			setText("What runtime are you using: nodejs, java8, or python 2.7?").
			setDef("nodejs").
			withCompleter(
			func(l string) []string {
				// there can only be one
				r := make([]string, 1)
				if len(l) == 0 {
					r[0] = ""
				} else {
					switch string(l[0]) {
					case "n":
						r[0] = "nodejs"
					case "j":
						r[0] = "java8"
					case "p":
						r[0] = "python2.7"
					}
				}

				return r
			},
		),
		newPrompt().
			withString(&cfg.EntryPoint).
			isRequired().
			setText("Enter an entry point or handler name").
			setDef("index.handler"),
		newPrompt().
			withInt(&cfg.MemorySize).
			setText("Enter memory size").
			setDef("128"),
		newPrompt().
			withInt(&cfg.Timeout).
			setText("Enter timeout").
			setDef("5"),
		newPrompt().
			withStringSet(&cfg.Regions).
			setText("Enter AWS regions where this function will run").
			setDef("us-east-1"),
		newPrompt().
			withFunc(
			func(s string) {
				// if this looks like an ARN,
				// we'll assume it is... for now
				if strings.Index(s, "arn:aws:iam::") == 0 {
					cfg.IamRole.Arn = &s
				} else {
					// check to see if this name is inside the role map
					if arn, ok := roleMap[s]; ok {
						cfg.IamRole.Arn = arn
					} else {
						// if not in role map, set the name
						cfg.IamRole.Name = &s
					}
				}
			},
		).
			isRequired().
			setText("IAM Role").
			setDescription(
			"\nAll Lambda functions must run with an IAM role, which gives them access\n" +
				"to various resources in AWS. What role do you want to assign this function?\n" +
				"Type a name and we'll try to auto-complete it if you press the tab key\n",
		).
			setDef("").
			withCompleter(
			func(l string) []string {
				c := make([]string, 0)
				for _, role := range iamRoles {
					if strings.HasPrefix(*role.Name, l) {
						c = append(c, *role.Name)
					}
				}

				return c
			},
		),
		newPrompt().
			withStringSet(&cfg.Location.S3Bucket).
			setText("s3 bucket you want to upload your code to (if any)").
			setDef(""),
		newPrompt().
			withStringSet(&cfg.Location.S3Key).
			setText("s3 folder your code should be stored inside (if any)").
			setDef(""),
		newPrompt().
			withStringSet(&cfg.Location.S3Region).
			setText("s3 region where your bucket is (if any)").
			setDef(""),
	}
}

// pulls all the IAM roles from your account
func getIamRoles() ([]*IamRole, map[string]*string) {
	debug := debug.Debug("core.getIamRoles")
	i := iam.New(nil)
	r, err := i.ListRoles(&iam.ListRolesInput{
		// try loading up to 1000 roles now
		MaxItems: aws.Int64(1000),
	})

	if err != nil {
		debug("getting IAM roles failed! maybe you don't have permission to do that?")
		return []*IamRole{}, map[string]*string{}
	}

	roles := make([]*IamRole, len(r.Roles))
	roleMap := make(map[string]*string)
	for i, r := range r.Roles {
		roles[i] = &IamRole{
			Arn:  r.Arn,
			Name: r.RoleName,
		}
		roleMap[*r.RoleName] = r.Arn
	}

	return roles, roleMap
}
<commit_msg>add prompts for s3<commit_after>package main

import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/spf13/cobra"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/peterh/liner"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/iam"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws"
import "github.com/hopkinsth/lambda-phage/Godeps/_workspace/src/github.com/tj/go-debug"
import "strconv"
import "strings"
import "fmt"
import "os"

func init() {
	initCmd := &cobra.Command{
		Use:   "init",
		Short: "initializes a config for your function",
		Run:   initPhage,
	}

	cmds = append(cmds, initCmd)
}

type prompt struct {
	text           string
	description    string
	def            string
	required       bool
	stringStore    **string
	stringSetStore *[]*string
	intStore       **int64
	funcStore      func(string)
	completer      func(string) []string
}

func newPrompt() *prompt {
	return new(prompt)
}

func (p *prompt) withCompleter(f liner.Completer) *prompt {
	p.completer = f
	return p
}

func (p *prompt) isRequired() *prompt {
	p.required = true
	return p
}

func (p *prompt) setDef(d string) *prompt {
	p.def = d
	return p
}

func (p *prompt) setText(t string) *prompt {
	p.text = t
	return p
}

func (p *prompt) setDescription(t string) *prompt {
	p.description = t
	return p
}

func (p *prompt) withString(s **string) *prompt {
	p.stringStore = s
	return p
}

func (p *prompt) withStringSet(s *[]*string) *prompt {
	p.stringSetStore = s
	return p
}

func (p *prompt) withInt(s **int64) *prompt {
	p.intStore = s
	return p
}

func (p *prompt) withFunc(s func(string)) *prompt {
	p.funcStore = s
	return p
}

// helps you build a config file
func initPhage(c *cobra.Command, _ []string) {
	var err error
	l := liner.NewLiner()
	defer l.Close()
	l.SetCtrlCAborts(true)
	fmt.Println(`
  HELLO AND WELCOME

  This command will help you set up your code for deployment to Lambda!

  But we need some information from you, like what you want to name
  your function and a few other things!

  Please answer the prompts as they appear below:
	`)

	//reqMsg := "Sorry, that field is required. Try again."

	// set this callback we can use to call all the stuff
	var realCompleter liner.Completer
	l.SetCompleter(func(line string) []string {
		if realCompleter != nil {
			return realCompleter(line)
		}
		return nil
	})

	iCfg := new(Config)
	iCfg.IamRole = new(IamRole)
	iCfg.Location = new(Location)
	prompts := getPrompts(iCfg)

	for _, cPrompt := range prompts {
		p := cPrompt
		text := p.text
		if p.def != "" {
			text += " [" + p.def + "]"
		}

		text += ": "

		realCompleter = nil
		if p.completer != nil {
			realCompleter = p.completer
		}

		if p.description != "" {
			fmt.Print(p.description)
		}

		if s, err := l.Prompt(text); err == nil {
			input := s
			hasInput := input != ""
			if p.stringStore != nil {
				if hasInput {
					*p.stringStore = &input
				} else {
					*p.stringStore = &p.def
				}

			} else if p.stringSetStore != nil {
				var splitMe string
				if hasInput {
					splitMe = input
				} else {
					splitMe = p.def
				}

				spl := strings.Split(splitMe, ",")
				pspl := make([]*string, len(spl))
				for i, v := range spl {
					// we need to set the value
					// in a variable local to this block
					// because the pointed-to value in
					// `v` will change on the next
					// loop iteration
					realVal := v
					pspl[i] = &realVal
				}

				*p.stringSetStore = pspl

			} else if p.intStore != nil {
				var tParse string
				if hasInput {
					tParse = input
				} else {
					tParse = p.def
				}

				i, _ := strconv.ParseInt(tParse, 10, 64)
				*p.intStore = &i
			} else if p.funcStore != nil {
				// just call the function, man
				p.funcStore(s)
			}
		} else if err == liner.ErrPromptAborted {
			fmt.Println("Aborted")
			return
		} else {
			fmt.Println("Error reading line: ", err)
			return
		}
	}

	// merge in any existing properties from the config object
	var wCfg *Config
	if cfg != nil {
		// if there's a config object, merge these two together
		cfg.merge(iCfg)
		wCfg = cfg
	} else {
		wCfg = iCfg
	}

	cfgFile, _ := c.Flags().GetString("config")
	err = wCfg.writeToFile(cfgFile)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Setup complete; saved config to %s\n", cfgFile)
}

// returns all the prompts needed for the `init` command
func getPrompts(cfg *Config) []*prompt {
	wd, _ := os.Getwd()
	st, _ := os.Stat(wd)

	iamRoles, roleMap := getIamRoles()

	return []*prompt{
		newPrompt().
			withString(&cfg.Name).
			isRequired().
			setText("Enter a project name").
			setDef(st.Name()),
		newPrompt().
			withString(&cfg.Description).
			setText("Enter a project description if you'd like").
			setDef(""),
		newPrompt().
			withString(&cfg.Archive).
			setText("Enter a archive name if you'd like").
			setDef(st.Name() + ".zip"),
		newPrompt().
			withString(&cfg.Runtime).
			isRequired().
			setText("What runtime are you using: nodejs, java8, or python 2.7?").
			setDef("nodejs").
			withCompleter(
			func(l string) []string {
				// there can only be one
				r := make([]string, 1)
				if len(l) == 0 {
					r[0] = ""
				} else {
					switch string(l[0]) {
					case "n":
						r[0] = "nodejs"
					case "j":
						r[0] = "java8"
					case "p":
						r[0] = "python2.7"
					}
				}

				return r
			},
		),
		newPrompt().
			withString(&cfg.EntryPoint).
			isRequired().
			setText("Enter an entry point or handler name").
			setDef("index.handler"),
		newPrompt().
			withInt(&cfg.MemorySize).
			setText("Enter memory size").
			setDef("128"),
		newPrompt().
			withInt(&cfg.Timeout).
			setText("Enter timeout").
			setDef("5"),
		newPrompt().
			withStringSet(&cfg.Regions).
			setText("Enter AWS regions where this function will run").
			setDef("us-east-1"),
		newPrompt().
			withFunc(
			func(s string) {
				// if this looks like an ARN,
				// we'll assume it is... for now
				if strings.Index(s, "arn:aws:iam::") == 0 {
					cfg.IamRole.Arn = &s
				} else {
					// check to see if this name is inside the role map
					if arn, ok := roleMap[s]; ok {
						cfg.IamRole.Arn = arn
					} else {
						// if not in role map, set the name
						cfg.IamRole.Name = &s
					}
				}
			},
		).
			isRequired().
			setText("IAM Role").
			setDescription(
			"\nAll Lambda functions must run with an IAM role, which gives them access\n" +
				"to various resources in AWS. What role do you want to assign this function?\n" +
				"Type a name and we'll try to auto-complete it if you press the tab key\n",
		).
			setDef("").
			withCompleter(
			func(l string) []string {
				c := make([]string, 0)
				for _, role := range iamRoles {
					if strings.HasPrefix(*role.Name, l) {
						c = append(c, *role.Name)
					}
				}

				return c
			},
		),
		newPrompt().
			withString(&cfg.Location.S3Bucket).
			setText("s3 bucket you want to upload your code to (if any)").
			setDef(""),
		newPrompt().
			withString(&cfg.Location.S3Key).
			setText("s3 folder your code should be stored inside (if any)").
			setDef(""),
		newPrompt().
			withString(&cfg.Location.S3Region).
			setText("s3 region where your bucket is (if any)").
			setDef(""),
	}
}

// pulls all the IAM roles from your account
func getIamRoles() ([]*IamRole, map[string]*string) {
	debug := debug.Debug("core.getIamRoles")
	i := iam.New(nil)
	r, err := i.ListRoles(&iam.ListRolesInput{
		// try loading up to 1000 roles now
		MaxItems: aws.Int64(1000),
	})

	if err != nil {
		debug("getting IAM roles failed! maybe you don't have permission to do that?")
		return []*IamRole{}, map[string]*string{}
	}

	roles := make([]*IamRole, len(r.Roles))
	roleMap := make(map[string]*string)
	for i, r := range r.Roles {
		roles[i] = &IamRole{
			Arn:  r.Arn,
			Name: r.RoleName,
		}
		roleMap[*r.RoleName] = r.Arn
	}

	return roles, roleMap
}
<|endoftext|>