<commit_before>package xhttp

import (
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/atdiar/goroutine/execution"
	"github.com/atdiar/sac"
)

// ServeMux holds the multiplexing logic of incoming http requests.
// It wraps around a net/http multiplexer.
// It facilitates the registration of request handlers.
type ServeMux struct {
	catchAll HandlerLinker
	handlers map[string]verbsHandler
	timeout  time.Duration
	*http.ServeMux
	pool *sync.Pool
}

type option func(*ServeMux)

// ChangeMux returns a configuration option for the ServeMux constructor
// which enables the choice of an alternate Muxer.
func ChangeMux(mux *http.ServeMux) func(*ServeMux) {
	return func(i *ServeMux) {
		i.ServeMux = mux
	}
}

// NewServeMux creates a new multiplexer which holds the request servicing logic.
// The mux used by default is http.DefaultServeMux.
// That can be changed by using the ChangeMux configuration option.
func NewServeMux(options ...option) ServeMux {
	sm := ServeMux{}
	sm.ServeMux = http.DefaultServeMux
	sm.handlers = make(map[string]verbsHandler)
	sm.pool = sac.Pool()

	// The below applies the options if any were passed.
	for _, opt := range options {
		opt(&sm)
	}
	return sm
}

// ServeHTTP is the request-servicing function for an object of type ServeMux.
func (sm ServeMux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Let's get the pattern first.
	_, pattern := sm.ServeMux.Handler(req)

	// Let's check whether a handler has been registered for this pattern.
	if vh, ok := sm.handlers[pattern]; ok {

		// Let's extract the http Method and apply the handler if it exists.
		method := strings.ToUpper(req.Method)
		switch method {
		case "GET":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.get.ServeHTTP(ctx, w, req)
		case "POST":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.post.ServeHTTP(ctx, w, req)
		case "PUT":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.put.ServeHTTP(ctx, w, req)
		case "PATCH":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.patch.ServeHTTP(ctx, w, req)
		case "DELETE":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.delete.ServeHTTP(ctx, w, req)
		case "HEAD":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.head.ServeHTTP(ctx, w, req)
		case "OPTIONS":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.options.ServeHTTP(ctx, w, req)
		case "CONNECT":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.connect.ServeHTTP(ctx, w, req)
		case "TRACE":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.trace.ServeHTTP(ctx, w, req)
		default:
			http.Error(w, http.StatusText(405), 405)
		}
	} else {
		http.Error(w, http.StatusText(405), 405)
	}
}

// verbsHandler defines the request handling that is attached to each http
// verb.
type verbsHandler struct {
	get     transformationHandler
	post    transformationHandler
	put     transformationHandler
	patch   transformationHandler
	delete  transformationHandler
	head    transformationHandler
	options transformationHandler
	connect transformationHandler
	trace   transformationHandler
}

func (vh *verbsHandler) prepend(h HandlerLinker) {
	vh.get.prepend(h)
	vh.post.prepend(h)
	vh.put.prepend(h)
	vh.patch.prepend(h)
	vh.delete.prepend(h)
	vh.head.prepend(h)
	vh.options.prepend(h)
	vh.connect.prepend(h)
	vh.trace.prepend(h)
}

// transformationHandler is defined per pattern and per verb.
// This format allows for the modification of a handler. For instance, it is
// used to prepend catchall request handlers more easily.
// It implements the Handler interface.
type transformationHandler struct {
	input   Handler
	Handler // output
}

func (t *transformationHandler) register(h Handler) {
	t.input = h
	t.Handler = h
}

func (t *transformationHandler) prepend(h HandlerLinker) {
	if h != nil && t.input != nil {
		t.Handler = h.CallNext(t.input)
	} else {
		panic("Nil handlers can't be linked together.")
	}
}

// HANDLER REGISTRATION

// GET registers the request Handler for the servicing of http GET requests.
func (sm *ServeMux) GET(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, sm)
	}

	routehandler.get.register(h)
	routehandler.get.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// POST registers the request Handler for the servicing of http POST requests.
func (sm *ServeMux) POST(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, sm)
	}

	routehandler.post.register(h)
	routehandler.post.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// PUT registers the request Handler for the servicing of http PUT requests.
func (sm *ServeMux) PUT(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.put.register(h)
	routehandler.put.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// PATCH registers the request Handler for the servicing of http PATCH requests.
func (sm *ServeMux) PATCH(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.patch.register(h)
	routehandler.patch.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// DELETE registers the request Handler for the servicing of http DELETE requests.
func (sm *ServeMux) DELETE(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.delete.register(h)
	routehandler.delete.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// HEAD registers the request Handler for the servicing of http HEAD requests.
func (sm *ServeMux) HEAD(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.head.register(h)
	routehandler.head.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// OPTIONS registers the request Handler for the servicing of http OPTIONS requests.
func (sm *ServeMux) OPTIONS(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.options.register(h)
	routehandler.options.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// CONNECT registers the request Handler for the servicing of http CONNECT requests.
func (sm *ServeMux) CONNECT(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.connect.register(h)
	routehandler.connect.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// TRACE registers the request Handler for the servicing of http TRACE requests.
func (sm *ServeMux) TRACE(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.trace.register(h)
	routehandler.trace.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// USE registers linkable request Handlers (i.e. implementing HandlerLinker)
// which shall be servicing any path, regardless of the request method.
func (sm *ServeMux) USE(handlers ...HandlerLinker) {
	sm.catchAll = Link(handlers...)
	for method, vh := range sm.handlers {
		vh.prepend(sm.catchAll)
		sm.handlers[method] = vh
	}
}

// Link is a function that is used to create a chain of Handlers when provided
// with linkable Handlers (they must implement HandlerLinker).
// It returns the first link of the chain.
func Link(handlers ...HandlerLinker) HandlerLinker {
	l := len(handlers)

	if l == 0 {
		return nil
	}

	if l > 1 {
		// Starting from the penultimate element, we link the handlers using the
		// CallNext registration method.
		for i := range handlers[:l-2] {
			h := handlers[l-2-i].CallNext(handlers[l-1-i])
			handlers[l-2-i] = h
		}
	}
	return handlers[0]
}
<commit_msg>Modify ServeHTTP to handle a third-party having registered a handler.<commit_after>package xhttp

import (
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/atdiar/goroutine/execution"
	"github.com/atdiar/sac"
)

// ServeMux holds the multiplexing logic of incoming http requests.
// It wraps around a net/http multiplexer.
// It facilitates the registration of request handlers.
type ServeMux struct {
	catchAll HandlerLinker
	handlers map[string]verbsHandler
	timeout  time.Duration
	*http.ServeMux
	pool *sync.Pool
}

type option func(*ServeMux)

// ChangeMux returns a configuration option for the ServeMux constructor
// which enables the choice of an alternate Muxer.
func ChangeMux(mux *http.ServeMux) func(*ServeMux) {
	return func(i *ServeMux) {
		i.ServeMux = mux
	}
}

// NewServeMux creates a new multiplexer which holds the request servicing logic.
// The mux used by default is http.DefaultServeMux.
// That can be changed by using the ChangeMux configuration option.
func NewServeMux(options ...option) ServeMux {
	sm := ServeMux{}
	sm.ServeMux = http.DefaultServeMux
	sm.handlers = make(map[string]verbsHandler)
	sm.pool = sac.Pool()

	// The below applies the options if any were passed.
	for _, opt := range options {
		opt(&sm)
	}
	return sm
}

// ServeHTTP is the request-servicing function for an object of type ServeMux.
func (sm ServeMux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Let's get the pattern first.
	_, pattern := sm.ServeMux.Handler(req)

	// Let's check whether a handler has been registered for this pattern.
	if vh, ok := sm.handlers[pattern]; ok {

		// Let's extract the http Method and apply the handler if it exists.
		method := strings.ToUpper(req.Method)
		switch method {
		case "GET":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.get.ServeHTTP(ctx, w, req)
		case "POST":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.post.ServeHTTP(ctx, w, req)
		case "PUT":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.put.ServeHTTP(ctx, w, req)
		case "PATCH":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.patch.ServeHTTP(ctx, w, req)
		case "DELETE":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.delete.ServeHTTP(ctx, w, req)
		case "HEAD":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.head.ServeHTTP(ctx, w, req)
		case "OPTIONS":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.options.ServeHTTP(ctx, w, req)
		case "CONNECT":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.connect.ServeHTTP(ctx, w, req)
		case "TRACE":
			S := sac.New(sm.pool)
			ctx := execution.NewContext(S)
			if sm.timeout != 0 {
				ctx = ctx.CancelAfter(execution.Timeout(sm.timeout))
			}
			defer ctx.Cancel()
			vh.trace.ServeHTTP(ctx, w, req)
		default:
			http.Error(w, http.StatusText(405), 405)
		}
	} else {
		// If nothing was registered by any other entity, h will default to
		// a page not found handler (404)
		h, _ := sm.ServeMux.Handler(req)
		h.ServeHTTP(w, req)
	}
}

// verbsHandler defines the request handling that is attached to each http
// verb.
type verbsHandler struct {
	get     transformationHandler
	post    transformationHandler
	put     transformationHandler
	patch   transformationHandler
	delete  transformationHandler
	head    transformationHandler
	options transformationHandler
	connect transformationHandler
	trace   transformationHandler
}

func (vh *verbsHandler) prepend(h HandlerLinker) {
	vh.get.prepend(h)
	vh.post.prepend(h)
	vh.put.prepend(h)
	vh.patch.prepend(h)
	vh.delete.prepend(h)
	vh.head.prepend(h)
	vh.options.prepend(h)
	vh.connect.prepend(h)
	vh.trace.prepend(h)
}

// transformationHandler is defined per pattern and per verb.
// This format allows for the modification of a handler. For instance, it is
// used to prepend catchall request handlers more easily.
// It implements the Handler interface.
type transformationHandler struct {
	input   Handler
	Handler // output
}

func (t *transformationHandler) register(h Handler) {
	t.input = h
	t.Handler = h
}

func (t *transformationHandler) prepend(h HandlerLinker) {
	if h != nil && t.input != nil {
		t.Handler = h.CallNext(t.input)
	} else {
		panic("Nil handlers can't be linked together.")
	}
}

// HANDLER REGISTRATION

// GET registers the request Handler for the servicing of http GET requests.
func (sm *ServeMux) GET(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, sm)
	}

	routehandler.get.register(h)
	routehandler.get.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// POST registers the request Handler for the servicing of http POST requests.
func (sm *ServeMux) POST(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, sm)
	}

	routehandler.post.register(h)
	routehandler.post.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// PUT registers the request Handler for the servicing of http PUT requests.
func (sm *ServeMux) PUT(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.put.register(h)
	routehandler.put.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// PATCH registers the request Handler for the servicing of http PATCH requests.
func (sm *ServeMux) PATCH(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.patch.register(h)
	routehandler.patch.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// DELETE registers the request Handler for the servicing of http DELETE requests.
func (sm *ServeMux) DELETE(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.delete.register(h)
	routehandler.delete.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// HEAD registers the request Handler for the servicing of http HEAD requests.
func (sm *ServeMux) HEAD(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.head.register(h)
	routehandler.head.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// OPTIONS registers the request Handler for the servicing of http OPTIONS requests.
func (sm *ServeMux) OPTIONS(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.options.register(h)
	routehandler.options.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// CONNECT registers the request Handler for the servicing of http CONNECT requests.
func (sm *ServeMux) CONNECT(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.connect.register(h)
	routehandler.connect.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// TRACE registers the request Handler for the servicing of http TRACE requests.
func (sm *ServeMux) TRACE(pattern string, h Handler) {

	if h == nil {
		panic("ERROR: Handler should not be nil.")
	}

	routehandler, ok := sm.handlers[pattern]
	if !ok {
		sm.ServeMux.Handle(pattern, *sm)
	}

	routehandler.trace.register(h)
	routehandler.trace.prepend(sm.catchAll)
	sm.handlers[pattern] = routehandler

}

// USE registers linkable request Handlers (i.e. implementing HandlerLinker)
// which shall be servicing any path, regardless of the request method.
func (sm *ServeMux) USE(handlers ...HandlerLinker) {
	sm.catchAll = Link(handlers...)
	for method, vh := range sm.handlers {
		vh.prepend(sm.catchAll)
		sm.handlers[method] = vh
	}
}

// Link is a function that is used to create a chain of Handlers when provided
// with linkable Handlers (they must implement HandlerLinker).
// It returns the first link of the chain.
func Link(handlers ...HandlerLinker) HandlerLinker {
	l := len(handlers)

	if l == 0 {
		return nil
	}

	if l > 1 {
		// Starting from the penultimate element, we link the handlers using the
		// CallNext registration method.
		for i := range handlers[:l-2] {
			h := handlers[l-2-i].CallNext(handlers[l-1-i])
			handlers[l-2-i] = h
		}
	}
	return handlers[0]
}
<|endoftext|>


<commit_before>/*
http://www.apache.org/licenses/LICENSE-2.0.txt

Copyright 2015 Intel Corporation

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

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

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

package mysql

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"

	"github.com/intelsdi-x/pulse/control/plugin"
	"github.com/intelsdi-x/pulse/control/plugin/cpolicy"
	"github.com/intelsdi-x/pulse/core/ctypes"

	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

const (
	name       = "mysql"
	version    = 3
	pluginType = plugin.PublisherPluginType
)

type mysqlPublisher struct {
}

func NewMySQLPublisher() *mysqlPublisher {
	return &mysqlPublisher{}
}

// Publish sends data to a MySQL server
func (s *mysqlPublisher) Publish(contentType string, content []byte, config map[string]ctypes.ConfigValue) error {
	logger := log.New()
	logger.Println("Publishing started")
	var metrics []plugin.PluginMetricType

	switch contentType {
	case plugin.PulseGOBContentType:
		dec := gob.NewDecoder(bytes.NewBuffer(content))
		if err := dec.Decode(&metrics); err != nil {
			logger.Printf("Error decoding: error=%v content=%v", err, content)
			return err
		}
	default:
		logger.Printf("Error unknown content type '%v'", contentType)
		return errors.New(fmt.Sprintf("Unknown content type '%s'", contentType))
	}

	logger.Printf("publishing %v to %v", metrics, config)

	// Open connection and ping to make sure it works
	username := config["username"].(ctypes.ConfigValueStr).Value
	password := config["password"].(ctypes.ConfigValueStr).Value
	database := config["database"].(ctypes.ConfigValueStr).Value
	tableName := config["tablename"].(ctypes.ConfigValueStr).Value
	tableColumns := "(timestamp VARCHAR(200), source_column VARCHAR(200), key_column VARCHAR(200), value_column VARCHAR(200))"
	db, err := sql.Open("mysql", username+":"+password+"@/"+database)
	defer db.Close()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	err = db.Ping()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	// Create the table if it's not already there
	_, err = db.Exec("CREATE TABLE IF NOT EXISTS" + " " + tableName + " " + tableColumns)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	// Put the values into the database with the current time
	tableValues := "VALUES( ?, ?, ?, ? )"
	insert, err := db.Prepare("INSERT INTO" + " " + tableName + " " + tableValues)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	var key, value string
	for _, m := range metrics {
		key = sliceToString(m.Namespace())
		value, err = interfaceToString(m.Data())
		if err != nil {
			logger.Printf("Error: %v", err)
			return err
		}
		_, err := insert.Exec(m.Timestamp(), m.Source(), key, value)
		if err != nil {
			panic(err)
		}

	}

	return nil
}

func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(name, version, pluginType, []string{plugin.PulseGOBContentType}, []string{plugin.PulseGOBContentType})
}

func (f *mysqlPublisher) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
	cp := cpolicy.New()
	config := cpolicy.NewPolicyNode()

	username, err := cpolicy.NewStringRule("username", true, "root")
	handleErr(err)
	username.Description = "Username to login to the MySQL server"

	password, err := cpolicy.NewStringRule("password", true, "root")
	handleErr(err)
	password.Description = "Password to login to the MySQL server"

	database, err := cpolicy.NewStringRule("database", true, "PULSE_TEST")
	handleErr(err)
	database.Description = "The MySQL database that data will be pushed to"

	tableName, err := cpolicy.NewStringRule("tablename", true, "info")
	handleErr(err)
	tableName.Description = "The MySQL table within the database where information will be stored"

	config.Add(username, password, database, tableName)

	cp.Add([]string{""}, config)
	return cp, nil
}

func handleErr(e error) {
	if e != nil {
		panic(e)
	}
}

func sliceToString(slice []string) string {
	return strings.Join(slice, ", ")
}

// Supported types: []string, []int, int, string
func interfaceToString(face interface{}) (string, error) {
	var (
		ret string
		err error
	)
	switch val := face.(type) {
	case []string:
		ret = sliceToString(val)
	case []int:
		length := len(val)
		if length == 0 {
			return ret, err
		}
		ret = strconv.Itoa(val[0])
		if length == 1 {
			return ret, err
		}
		for i := 1; i < length; i++ {
			ret += ", "
			ret += strconv.Itoa(val[i])
		}
	case int:
		ret = strconv.Itoa(val)
	case string:
		ret = val
	default:
		err = errors.New("unsupported type")
	}
	return ret, err
}
<commit_msg>Update version to 4<commit_after>/*
http://www.apache.org/licenses/LICENSE-2.0.txt

Copyright 2015 Intel Corporation

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

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

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

package mysql

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"

	"github.com/intelsdi-x/pulse/control/plugin"
	"github.com/intelsdi-x/pulse/control/plugin/cpolicy"
	"github.com/intelsdi-x/pulse/core/ctypes"

	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

const (
	name       = "mysql"
	version    = 4
	pluginType = plugin.PublisherPluginType
)

type mysqlPublisher struct {
}

func NewMySQLPublisher() *mysqlPublisher {
	return &mysqlPublisher{}
}

// Publish sends data to a MySQL server
func (s *mysqlPublisher) Publish(contentType string, content []byte, config map[string]ctypes.ConfigValue) error {
	logger := log.New()
	logger.Println("Publishing started")
	var metrics []plugin.PluginMetricType

	switch contentType {
	case plugin.PulseGOBContentType:
		dec := gob.NewDecoder(bytes.NewBuffer(content))
		if err := dec.Decode(&metrics); err != nil {
			logger.Printf("Error decoding: error=%v content=%v", err, content)
			return err
		}
	default:
		logger.Printf("Error unknown content type '%v'", contentType)
		return errors.New(fmt.Sprintf("Unknown content type '%s'", contentType))
	}

	logger.Printf("publishing %v to %v", metrics, config)

	// Open connection and ping to make sure it works
	username := config["username"].(ctypes.ConfigValueStr).Value
	password := config["password"].(ctypes.ConfigValueStr).Value
	database := config["database"].(ctypes.ConfigValueStr).Value
	tableName := config["tablename"].(ctypes.ConfigValueStr).Value
	tableColumns := "(timestamp VARCHAR(200), source_column VARCHAR(200), key_column VARCHAR(200), value_column VARCHAR(200))"
	db, err := sql.Open("mysql", username+":"+password+"@/"+database)
	defer db.Close()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	err = db.Ping()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	// Create the table if it's not already there
	_, err = db.Exec("CREATE TABLE IF NOT EXISTS" + " " + tableName + " " + tableColumns)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	// Put the values into the database with the current time
	tableValues := "VALUES( ?, ?, ?, ? )"
	insert, err := db.Prepare("INSERT INTO" + " " + tableName + " " + tableValues)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	var key, value string
	for _, m := range metrics {
		key = sliceToString(m.Namespace())
		value, err = interfaceToString(m.Data())
		if err != nil {
			logger.Printf("Error: %v", err)
			return err
		}
		_, err := insert.Exec(m.Timestamp(), m.Source(), key, value)
		if err != nil {
			panic(err)
		}

	}

	return nil
}

func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(name, version, pluginType, []string{plugin.PulseGOBContentType}, []string{plugin.PulseGOBContentType})
}

func (f *mysqlPublisher) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
	cp := cpolicy.New()
	config := cpolicy.NewPolicyNode()

	username, err := cpolicy.NewStringRule("username", true, "root")
	handleErr(err)
	username.Description = "Username to login to the MySQL server"

	password, err := cpolicy.NewStringRule("password", true, "root")
	handleErr(err)
	password.Description = "Password to login to the MySQL server"

	database, err := cpolicy.NewStringRule("database", true, "PULSE_TEST")
	handleErr(err)
	database.Description = "The MySQL database that data will be pushed to"

	tableName, err := cpolicy.NewStringRule("tablename", true, "info")
	handleErr(err)
	tableName.Description = "The MySQL table within the database where information will be stored"

	config.Add(username, password, database, tableName)

	cp.Add([]string{""}, config)
	return cp, nil
}

func handleErr(e error) {
	if e != nil {
		panic(e)
	}
}

func sliceToString(slice []string) string {
	return strings.Join(slice, ", ")
}

// Supported types: []string, []int, int, string
func interfaceToString(face interface{}) (string, error) {
	var (
		ret string
		err error
	)
	switch val := face.(type) {
	case []string:
		ret = sliceToString(val)
	case []int:
		length := len(val)
		if length == 0 {
			return ret, err
		}
		ret = strconv.Itoa(val[0])
		if length == 1 {
			return ret, err
		}
		for i := 1; i < length; i++ {
			ret += ", "
			ret += strconv.Itoa(val[i])
		}
	case int:
		ret = strconv.Itoa(val)
	case string:
		ret = val
	default:
		err = errors.New("unsupported type")
	}
	return ret, err
}
<|endoftext|>


<commit_before>package mysql

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/intelsdi-x/pulse/control/plugin"
	"github.com/intelsdi-x/pulse/control/plugin/cpolicy"
	"github.com/intelsdi-x/pulse/core/ctypes"

	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

const (
	name       = "mysql"
	version    = 1
	pluginType = plugin.PublisherPluginType
)

type mysqlPublisher struct {
}

func NewMySQLPublisher() *mysqlPublisher {
	return &mysqlPublisher{}
}

// Publish sends data to a MySQL server
func (s *mysqlPublisher) Publish(contentType string, content []byte, config map[string]ctypes.ConfigValue) error {
	logger := log.New()
	logger.Println("Publishing started")
	var metrics []plugin.PluginMetricType

	switch contentType {
	case plugin.PulseGOBContentType:
		dec := gob.NewDecoder(bytes.NewBuffer(content))
		if err := dec.Decode(&metrics); err != nil {
			logger.Printf("Error decoding: error=%v content=%v", err, content)
			return err
		}
	default:
		logger.Printf("Error unknown content type '%v'", contentType)
		return errors.New(fmt.Sprintf("Unknown content type '%s'", contentType))
	}

	logger.Printf("publishing %v to %v", metrics, config)

	// Open connection and ping to make sure it works
	username := config["username"].(ctypes.ConfigValueStr).Value
	password := config["password"].(ctypes.ConfigValueStr).Value
	database := config["database"].(ctypes.ConfigValueStr).Value
	tableName := config["table name"].(ctypes.ConfigValueStr).Value
	tableColumns := "(time_posted VARCHAR(200), key_column VARCHAR(200), value_column VARCHAR(200))"
	db, err := sql.Open("mysql", username+":"+password+"@/"+database)
	defer db.Close()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	err = db.Ping()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	// Create the table if it's not already there
	_, err = db.Exec("CREATE TABLE IF NOT EXISTS" + " " + tableName + " " + tableColumns)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	// Put the values into the database with the current time
	tableValues := "VALUES( ?, ?, ? )"
	insert, err := db.Prepare("INSERT INTO" + " " + tableName + " " + tableValues)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	nowTime := time.Now()
	var key, value string
	for _, m := range metrics {
		key = sliceToString(m.Namespace())
		value, err = interfaceToString(m.Data())
		if err == nil {
			_, err := insert.Exec(nowTime, key, value)
			if err != nil {
				panic(err)
				logger.Printf("Error: %v", err)
			}
		} else {
			logger.Printf("Error: %v", err)
		}
	}

	return nil
}

func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(name, version, pluginType, []string{plugin.PulseGOBContentType}, []string{plugin.PulseGOBContentType})
}

func (f *mysqlPublisher) GetConfigPolicy() cpolicy.ConfigPolicy {
	cp := cpolicy.New()
	config := cpolicy.NewPolicyNode()

	username, err := cpolicy.NewStringRule("username", true, "root")
	handleErr(err)
	username.Description = "Username to login to the MySQL server"

	password, err := cpolicy.NewStringRule("password", true, "root")
	handleErr(err)
	password.Description = "Password to login to the MySQL server"

	database, err := cpolicy.NewStringRule("database", true, "PULSE_TEST")
	handleErr(err)
	database.Description = "The MySQL database that data will be pushed to"

	tableName, err := cpolicy.NewStringRule("tablename", true, "info")
	handleErr(err)
	tableName.Description = "The MySQL table within the database where information will be stored"

	config.Add(username, password, database, tableName)

	cp.Add([]string{""}, config)
	return *cp
}

func handleErr(e error) {
	if e != nil {
		panic(e)
	}
}

func sliceToString(slice []string) string {
	return strings.Join(slice, ", ")
}

// Supported types: []string, []int, int, string
func interfaceToString(face interface{}) (string, error) {
	var (
		ret string
		err error
	)
	switch val := face.(type) {
	case []string:
		ret = sliceToString(val)
	case []int:
		length := len(val)
		if length == 0 {
			return ret, err
		}
		ret = strconv.Itoa(val[0])
		if length == 1 {
			return ret, err
		}
		for i := 1; i < length; i++ {
			ret += ", "
			ret += strconv.Itoa(val[i])
		}
	case int:
		ret = strconv.Itoa(val)
	case string:
		ret = val
	default:
		err = errors.New("unsupported type")
	}
	return ret, err
}
<commit_msg>Update with code fixes from go vet and add source and timestamp from metric<commit_after>package mysql

import (
	"bytes"
	"encoding/gob"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/intelsdi-x/pulse/control/plugin"
	"github.com/intelsdi-x/pulse/control/plugin/cpolicy"
	"github.com/intelsdi-x/pulse/core/ctypes"

	"database/sql"

	_ "github.com/go-sql-driver/mysql"
)

const (
	name       = "mysql"
	version    = 1
	pluginType = plugin.PublisherPluginType
)

type mysqlPublisher struct {
}

func NewMySQLPublisher() *mysqlPublisher {
	return &mysqlPublisher{}
}

// Publish sends data to a MySQL server
func (s *mysqlPublisher) Publish(contentType string, content []byte, config map[string]ctypes.ConfigValue) error {
	logger := log.New()
	logger.Println("Publishing started")
	var metrics []plugin.PluginMetricType

	switch contentType {
	case plugin.PulseGOBContentType:
		dec := gob.NewDecoder(bytes.NewBuffer(content))
		if err := dec.Decode(&metrics); err != nil {
			logger.Printf("Error decoding: error=%v content=%v", err, content)
			return err
		}
	default:
		logger.Printf("Error unknown content type '%v'", contentType)
		return errors.New(fmt.Sprintf("Unknown content type '%s'", contentType))
	}

	logger.Printf("publishing %v to %v", metrics, config)

	// Open connection and ping to make sure it works
	username := config["username"].(ctypes.ConfigValueStr).Value
	password := config["password"].(ctypes.ConfigValueStr).Value
	database := config["database"].(ctypes.ConfigValueStr).Value
	tableName := config["tablename"].(ctypes.ConfigValueStr).Value
	tableColumns := "(timestamp VARCHAR(200), source_column VARCHAR(200), key_column VARCHAR(200), value_column VARCHAR(200))"
	db, err := sql.Open("mysql", username+":"+password+"@/"+database)
	defer db.Close()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	err = db.Ping()
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	// Create the table if it's not already there
	_, err = db.Exec("CREATE TABLE IF NOT EXISTS" + " " + tableName + " " + tableColumns)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}

	// Put the values into the database with the current time
	tableValues := "VALUES( ?, ?, ?, ? )"
	insert, err := db.Prepare("INSERT INTO" + " " + tableName + " " + tableValues)
	if err != nil {
		logger.Printf("Error: %v", err)
		return err
	}
	nowTime := time.Now()
	var key, value string
	for _, m := range metrics {
		key = sliceToString(m.Namespace())
		value, err = interfaceToString(m.Data())
		if err == nil {
			_, err := insert.Exec(m.Timestamp(), m.Source(), key, value)
			if err != nil {
				panic(err)
			}
		} else {
			logger.Printf("Error: %v", err)
		}
	}

	return nil
}

func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(name, version, pluginType, []string{plugin.PulseGOBContentType}, []string{plugin.PulseGOBContentType})
}

func (f *mysqlPublisher) GetConfigPolicy() cpolicy.ConfigPolicy {
	cp := cpolicy.New()
	config := cpolicy.NewPolicyNode()

	username, err := cpolicy.NewStringRule("username", true, "root")
	handleErr(err)
	username.Description = "Username to login to the MySQL server"

	password, err := cpolicy.NewStringRule("password", true, "root")
	handleErr(err)
	password.Description = "Password to login to the MySQL server"

	database, err := cpolicy.NewStringRule("database", true, "PULSE_TEST")
	handleErr(err)
	database.Description = "The MySQL database that data will be pushed to"

	tableName, err := cpolicy.NewStringRule("tablename", true, "info")
	handleErr(err)
	tableName.Description = "The MySQL table within the database where information will be stored"

	config.Add(username, password, database, tableName)

	cp.Add([]string{""}, config)
	return *cp
}

func handleErr(e error) {
	if e != nil {
		panic(e)
	}
}

func sliceToString(slice []string) string {
	return strings.Join(slice, ", ")
}

// Supported types: []string, []int, int, string
func interfaceToString(face interface{}) (string, error) {
	var (
		ret string
		err error
	)
	switch val := face.(type) {
	case []string:
		ret = sliceToString(val)
	case []int:
		length := len(val)
		if length == 0 {
			return ret, err
		}
		ret = strconv.Itoa(val[0])
		if length == 1 {
			return ret, err
		}
		for i := 1; i < length; i++ {
			ret += ", "
			ret += strconv.Itoa(val[i])
		}
	case int:
		ret = strconv.Itoa(val)
	case string:
		ret = val
	default:
		err = errors.New("unsupported type")
	}
	return ret, err
}
<|endoftext|>


<commit_before>package weatherdata

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

var (
	cacheFile  = fmt.Sprintf("%swoozyforecast.xml", os.TempDir())
	dateFormat = "2006-01-02T15:04:05"
)

type weatherCredit struct {
	URL  string `xml:"url,attr"`
	Text string `xml:"text,attr"`
}

type customTime struct {
	time.Time
}

type customTimeAttr struct {
	time.Time
}

type sun struct {
	Rise customTimeAttr `xml:"rise,attr"`
	Set  customTimeAttr `xml:"set,attr"`
}

type weatherLocation struct {
	Name     string `xml:"name"`
	Type     string `xml:"type"`
	Country  string `xml:"country"`
	Timezone struct {
		ID        string `xml:"id,attr"`
		UTCOffset string `xml:"utcoffsetMinutes,attr"`
	} `xml:"timezone"`
}

// WeatherMeta contains metadata about the forecast
type WeatherMeta struct {
	LastUpdate customTime `xml:"lastupdate"`
	NextUpdate customTime `xml:"nextupdate"`
}

// WeatherForecast contains actual forecast
type WeatherForecast struct {
	From     customTimeAttr `xml:"from,attr"`
	To       customTimeAttr `xml:"to,attr"`
	Period   int            `xml:"period,attr"`
	Pressure struct {
		Unit  string  `xml:"unit,attr"`
		Value float32 `xml:"value,attr"`
	} `xml:"pressure"`
	Precipitation struct {
		Value float32 `xml:"value,attr"`
		Min   float32 `xml:"minvalue,attr"`
		Max   float32 `xml:"maxvalue,attr"`
	} `xml:"precipitation"`
	Symbol struct {
		Name   string `xml:"name,attr"`
		Number int    `xml:"number,attr"`
	} `xml:"symbol"`
	Temperature struct {
		Unit  string `xml:"unit,attr"`
		Value int    `xml:"value,attr"`
	} `xml:"temperature"`
	WindDirection struct {
		Deg  float32 `xml:"deg,attr"`
		Code string  `xml:"code,attr"`
		Name string  `xml:"name,attr"`
	} `xml:"windDirection"`
	WindSpeed struct {
		Mps  float32 `xml:"mps,attr"`
		Name string  `xml:"name,attr"`
	} `xml:"windSpeed"`
}

// WeatherData contains actual weather data
type WeatherData struct {
	Credit   weatherCredit     `xml:"credit>link"`
	Location weatherLocation   `xml:"location"`
	Meta     WeatherMeta       `xml:"meta"`
	Sun      sun               `xml:"sun"`
	Forecast []WeatherForecast `xml:"forecast>tabular>time"`
}

func (c *customTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v string
	d.DecodeElement(&v, &start)
	loc, _ := time.LoadLocation("Local")
	parse, err := time.ParseInLocation(dateFormat, v, loc)
	if err != nil {
		return err
	}
	*c = customTime{parse}
	return nil
}

func (ca *customTimeAttr) UnmarshalXMLAttr(attr xml.Attr) error {
	loc, _ := time.LoadLocation("Local")
	parse, err := time.ParseInLocation(dateFormat, attr.Value, loc)
	if err != nil {
		return err
	}
	*ca = customTimeAttr{parse}
	return nil
}

// PeriodName returns period as text
func (wf WeatherForecast) PeriodName() string {
	var retStr string
	switch {
	case wf.Period == 0:
		retStr = "Night:   "
	case wf.Period == 1:
		retStr = "Morning: "
	case wf.Period == 2:
		retStr = "Day:     "
	case wf.Period == 3:
		retStr = "Evening: "
	}
	return retStr
}

// HoursSinceUpdate returns hours since last update
func (wm WeatherMeta) HoursSinceUpdate() float64 {
	t := time.Unix(0, wm.LastUpdate.Local().UnixNano())
	elapsed := time.Since(t)
	return elapsed.Hours()
}

// HoursToNextUpdate returns hours until next update
func (wm WeatherMeta) HoursToNextUpdate() float64 {
	t := time.Unix(0, wm.NextUpdate.Local().UnixNano())
	elapsed := t.Sub(time.Now())
	return elapsed.Hours()
}

// SunHours represents number of hours sun is up
func (wd WeatherData) SunHours() float64 {
	t := time.Unix(0, wd.Sun.Set.Local().UnixNano())
	elapsed := t.Sub(time.Unix(0, wd.Sun.Rise.Local().UnixNano()))
	return elapsed.Hours()
}

func yrURL(place string) string {
	return fmt.Sprintf("http://www.yr.no/place/%s/forecast.xml", place)
}

func weatherDataCache(cc bool, assumeValid bool) (wd WeatherData, valid bool) {
	if cc == true {
		fmt.Println("Clearing cache")
		os.Remove(cacheFile)
		return wd, false
	}
	xmlFile, err := os.Open(cacheFile)

	if err != nil {
		return wd, false
	}

	defer xmlFile.Close()
	XMLdata, _ := ioutil.ReadAll(xmlFile)
	xml.Unmarshal(XMLdata, &wd)

	// Check if forecast is still valid
	if wd.Meta.NextUpdate.Before(time.Now()) == true && assumeValid == false {
		os.Remove(cacheFile)
		return WeatherData{}, false
	}
	return wd, true
}

func fillWeatherDataCache(place string) (err error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", yrURL(place), nil)
	if err != nil {
		return err
	}

	req.Header.Set("User-Agent", "woozy, https://github.com/gummiboll/woozy")
	resp, err := client.Do(req)

	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New("Failed to load forecast from yr.no")
	}

	defer resp.Body.Close()
	out, err := os.Create(cacheFile)
	if err != nil {
		return err
	}
	defer out.Close()
	io.Copy(out, resp.Body)
	return nil
}

// LoadWeatherData loads xml from yr and returns a struct
func LoadWeatherData(place string, cc bool) (wd WeatherData, err error) {
	wd, valid := weatherDataCache(cc, false)
	if valid != true {
		err := fillWeatherDataCache(place)
		if err != nil {
			return wd, err
		}
		wd, valid := weatherDataCache(false, true)
		if valid != true {
			return wd, errors.New("Failed to load forecast")
		}
		return wd, nil
	}
	return wd, nil
}
<commit_msg>Adds a timeout to the yr.no-request<commit_after>package weatherdata

import (
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

var (
	cacheFile  = fmt.Sprintf("%swoozyforecast.xml", os.TempDir())
	dateFormat = "2006-01-02T15:04:05"
)

type weatherCredit struct {
	URL  string `xml:"url,attr"`
	Text string `xml:"text,attr"`
}

type customTime struct {
	time.Time
}

type customTimeAttr struct {
	time.Time
}

type sun struct {
	Rise customTimeAttr `xml:"rise,attr"`
	Set  customTimeAttr `xml:"set,attr"`
}

type weatherLocation struct {
	Name     string `xml:"name"`
	Type     string `xml:"type"`
	Country  string `xml:"country"`
	Timezone struct {
		ID        string `xml:"id,attr"`
		UTCOffset string `xml:"utcoffsetMinutes,attr"`
	} `xml:"timezone"`
}

// WeatherMeta contains metadata about the forecast
type WeatherMeta struct {
	LastUpdate customTime `xml:"lastupdate"`
	NextUpdate customTime `xml:"nextupdate"`
}

// WeatherForecast contains actual forecast
type WeatherForecast struct {
	From     customTimeAttr `xml:"from,attr"`
	To       customTimeAttr `xml:"to,attr"`
	Period   int            `xml:"period,attr"`
	Pressure struct {
		Unit  string  `xml:"unit,attr"`
		Value float32 `xml:"value,attr"`
	} `xml:"pressure"`
	Precipitation struct {
		Value float32 `xml:"value,attr"`
		Min   float32 `xml:"minvalue,attr"`
		Max   float32 `xml:"maxvalue,attr"`
	} `xml:"precipitation"`
	Symbol struct {
		Name   string `xml:"name,attr"`
		Number int    `xml:"number,attr"`
	} `xml:"symbol"`
	Temperature struct {
		Unit  string `xml:"unit,attr"`
		Value int    `xml:"value,attr"`
	} `xml:"temperature"`
	WindDirection struct {
		Deg  float32 `xml:"deg,attr"`
		Code string  `xml:"code,attr"`
		Name string  `xml:"name,attr"`
	} `xml:"windDirection"`
	WindSpeed struct {
		Mps  float32 `xml:"mps,attr"`
		Name string  `xml:"name,attr"`
	} `xml:"windSpeed"`
}

// WeatherData contains actual weather data
type WeatherData struct {
	Credit   weatherCredit     `xml:"credit>link"`
	Location weatherLocation   `xml:"location"`
	Meta     WeatherMeta       `xml:"meta"`
	Sun      sun               `xml:"sun"`
	Forecast []WeatherForecast `xml:"forecast>tabular>time"`
}

func (c *customTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v string
	d.DecodeElement(&v, &start)
	loc, _ := time.LoadLocation("Local")
	parse, err := time.ParseInLocation(dateFormat, v, loc)
	if err != nil {
		return err
	}
	*c = customTime{parse}
	return nil
}

func (ca *customTimeAttr) UnmarshalXMLAttr(attr xml.Attr) error {
	loc, _ := time.LoadLocation("Local")
	parse, err := time.ParseInLocation(dateFormat, attr.Value, loc)
	if err != nil {
		return err
	}
	*ca = customTimeAttr{parse}
	return nil
}

// PeriodName returns period as text
func (wf WeatherForecast) PeriodName() string {
	var retStr string
	switch {
	case wf.Period == 0:
		retStr = "Night:   "
	case wf.Period == 1:
		retStr = "Morning: "
	case wf.Period == 2:
		retStr = "Day:     "
	case wf.Period == 3:
		retStr = "Evening: "
	}
	return retStr
}

// HoursSinceUpdate returns hours since last update
func (wm WeatherMeta) HoursSinceUpdate() float64 {
	t := time.Unix(0, wm.LastUpdate.Local().UnixNano())
	elapsed := time.Since(t)
	return elapsed.Hours()
}

// HoursToNextUpdate returns hours until next update
func (wm WeatherMeta) HoursToNextUpdate() float64 {
	t := time.Unix(0, wm.NextUpdate.Local().UnixNano())
	elapsed := t.Sub(time.Now())
	return elapsed.Hours()
}

// SunHours represents number of hours sun is up
func (wd WeatherData) SunHours() float64 {
	t := time.Unix(0, wd.Sun.Set.Local().UnixNano())
	elapsed := t.Sub(time.Unix(0, wd.Sun.Rise.Local().UnixNano()))
	return elapsed.Hours()
}

func yrURL(place string) string {
	return fmt.Sprintf("http://www.yr.no/place/%s/forecast.xml", place)
}

func weatherDataCache(cc bool, assumeValid bool) (wd WeatherData, valid bool) {
	if cc == true {
		fmt.Println("Clearing cache")
		os.Remove(cacheFile)
		return wd, false
	}
	xmlFile, err := os.Open(cacheFile)

	if err != nil {
		return wd, false
	}

	defer xmlFile.Close()
	XMLdata, _ := ioutil.ReadAll(xmlFile)
	xml.Unmarshal(XMLdata, &wd)

	// Check if forecast is still valid
	if wd.Meta.NextUpdate.Before(time.Now()) == true && assumeValid == false {
		os.Remove(cacheFile)
		return WeatherData{}, false
	}
	return wd, true
}

func fillWeatherDataCache(place string) (err error) {
	client := &http.Client{
		Timeout: 20 * time.Second,
	}
	req, err := http.NewRequest("GET", yrURL(place), nil)
	if err != nil {
		return err
	}

	req.Header.Set("User-Agent", "woozy, https://github.com/gummiboll/woozy")
	resp, err := client.Do(req)

	if err != nil {
		return err
	}

	if resp.StatusCode != 200 {
		return errors.New("Failed to load forecast from yr.no")
	}

	defer resp.Body.Close()
	out, err := os.Create(cacheFile)
	if err != nil {
		return err
	}
	defer out.Close()
	io.Copy(out, resp.Body)
	return nil
}

// LoadWeatherData loads xml from yr and returns a struct
func LoadWeatherData(place string, cc bool) (wd WeatherData, err error) {
	wd, valid := weatherDataCache(cc, false)
	if valid != true {
		err := fillWeatherDataCache(place)
		if err != nil {
			return wd, err
		}
		wd, valid := weatherDataCache(false, true)
		if valid != true {
			return wd, errors.New("Failed to load forecast")
		}
		return wd, nil
	}
	return wd, nil
}
<|endoftext|>


<commit_before>package plugins

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/Jeffail/gabs"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/bwmarrin/discordgo"
)

const (
	streamableApiBaseUrl = "https://api.streamable.com/%s"
)

type Streamable struct{}

func (s *Streamable) Commands() []string {
	return []string{
		"streamable",
	}
}

func (s *Streamable) Init(session *discordgo.Session) {

}

func (s *Streamable) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) { // [p]streamable [<link>] or attachment
	var err error

	session.ChannelTyping(msg.ChannelID)

	if len(content) <= 0 && len(msg.Attachments) <= 0 {
		_, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
		helpers.Relax(err)
		return
	}

	sourceUrl := content
	if len(msg.Attachments) > 0 {
		sourceUrl = msg.Attachments[0].URL
	}

	createStreamableEndpoint := fmt.Sprintf(streamableApiBaseUrl, fmt.Sprintf("import?url=%s", url.QueryEscape(sourceUrl)))
	request, err := http.NewRequest("GET", createStreamableEndpoint, nil)
	request.Header.Add("user-agent", helpers.DEFAULT_UA)
	request.SetBasicAuth(helpers.GetConfig().Path("streamable.username").Data().(string),
		helpers.GetConfig().Path("streamable.password").Data().(string))
	helpers.Relax(err)
	response, err := httpClient.Do(request)
	helpers.Relax(err)
	defer response.Body.Close()
	buf := bytes.NewBuffer(nil)
	_, err = io.Copy(buf, response.Body)
	helpers.Relax(err)

	jsonResult, err := gabs.ParseJSON(buf.Bytes())

	if err != nil || jsonResult.ExistsP("status") == false || jsonResult.Path("status").Data().(float64) >= 3 {
		_, err = session.ChannelMessageSend(msg.ChannelID,
			fmt.Sprintf("<@%s> Something went wrong while creating your streamable. <:blobscream:317043778823389184>",
				msg.Author.ID))
		helpers.Relax(err)
		return
	}

	session.ChannelMessageSend(msg.ChannelID, "Your streamable is processing, this may take a while. <:blobsleeping:317047101534109696>")
	session.ChannelTyping(msg.ChannelID)

	streamableShortcode := jsonResult.Path("shortcode").Data().(string)
	streamableUrl := ""
CheckStreamableStatusLoop:
	for {
		statusStreamableEndpoint := fmt.Sprintf(streamableApiBaseUrl, fmt.Sprintf("videos/%s", streamableShortcode))
		result := helpers.GetJSON(statusStreamableEndpoint)

		switch result.Path("status").Data().(float64) {
		case 0:
		case 1:
			time.Sleep(5 * time.Second)
			session.ChannelTyping(msg.ChannelID)
			continue CheckStreamableStatusLoop
		case 2:
			streamableUrl = result.Path("url").Data().(string)
			if !strings.Contains(streamableUrl, "://") {
				streamableUrl = "https://" + streamableUrl
			}
			break CheckStreamableStatusLoop
		default:
			_, err = session.ChannelMessageSend(msg.ChannelID,
				fmt.Sprintf("<@%s> Something went wrong while creating your streamable. <:blobscream:317043778823389184>",
					msg.Author.ID))
			helpers.Relax(err)
			return
			return
		}
	}

	_, err = session.ChannelMessageSend(msg.ChannelID, fmt.Sprintf("<@%s> Your streamable is done: %s .", msg.Author.ID, streamableUrl))
	helpers.Relax(err)
}
<commit_msg>[streamable] handles 429 error<commit_after>package plugins

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/Jeffail/gabs"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/bwmarrin/discordgo"
)

const (
	streamableApiBaseUrl = "https://api.streamable.com/%s"
)

type Streamable struct{}

func (s *Streamable) Commands() []string {
	return []string{
		"streamable",
	}
}

func (s *Streamable) Init(session *discordgo.Session) {

}

func (s *Streamable) Action(command string, content string, msg *discordgo.Message, session *discordgo.Session) { // [p]streamable [<link>] or attachment
	var err error

	session.ChannelTyping(msg.ChannelID)

	if len(content) <= 0 && len(msg.Attachments) <= 0 {
		_, err := session.ChannelMessageSend(msg.ChannelID, helpers.GetText("bot.arguments.invalid"))
		helpers.Relax(err)
		return
	}

	sourceUrl := content
	if len(msg.Attachments) > 0 {
		sourceUrl = msg.Attachments[0].URL
	}

	createStreamableEndpoint := fmt.Sprintf(streamableApiBaseUrl, fmt.Sprintf("import?url=%s", url.QueryEscape(sourceUrl)))
	request, err := http.NewRequest("GET", createStreamableEndpoint, nil)
	request.Header.Add("user-agent", helpers.DEFAULT_UA)
	request.SetBasicAuth(helpers.GetConfig().Path("streamable.username").Data().(string),
		helpers.GetConfig().Path("streamable.password").Data().(string))
	helpers.Relax(err)
	response, err := httpClient.Do(request)
	helpers.Relax(err)
	defer response.Body.Close()
	buf := bytes.NewBuffer(nil)
	_, err = io.Copy(buf, response.Body)
	helpers.Relax(err)

	jsonResult, err := gabs.ParseJSON(buf.Bytes())

	if err != nil || jsonResult.ExistsP("status") == false || jsonResult.Path("status").Data().(float64) >= 3 {
		_, err = session.ChannelMessageSend(msg.ChannelID,
			fmt.Sprintf("<@%s> Something went wrong while creating your streamable. <:blobscream:317043778823389184>",
				msg.Author.ID))
		helpers.Relax(err)
		return
	}

	session.ChannelMessageSend(msg.ChannelID, "Your streamable is processing, this may take a while. <:blobsleeping:317047101534109696>")
	session.ChannelTyping(msg.ChannelID)

	streamableShortcode := jsonResult.Path("shortcode").Data().(string)
	streamableUrl := ""
CheckStreamableStatusLoop:
	for {
		statusStreamableEndpoint := fmt.Sprintf(streamableApiBaseUrl, fmt.Sprintf("videos/%s", streamableShortcode))
		result, err := gabs.ParseJSON(helpers.NetGet(statusStreamableEndpoint))
		if err != nil {
			if strings.Contains(err.Error(), "Expected status 200; Got 429") {
				_, err = session.ChannelMessageSend(msg.ChannelID,
					fmt.Sprintf("<@%s> Too many requests, please try again later. <:blobscream:317043778823389184>",
						msg.Author.ID))
				helpers.Relax(err)
			} else {
				helpers.Relax(err)
			}
		}

		switch result.Path("status").Data().(float64) {
		case 0:
		case 1:
			time.Sleep(5 * time.Second)
			session.ChannelTyping(msg.ChannelID)
			continue CheckStreamableStatusLoop
		case 2:
			streamableUrl = result.Path("url").Data().(string)
			if !strings.Contains(streamableUrl, "://") {
				streamableUrl = "https://" + streamableUrl
			}
			break CheckStreamableStatusLoop
		default:
			_, err = session.ChannelMessageSend(msg.ChannelID,
				fmt.Sprintf("<@%s> Something went wrong while creating your streamable. <:blobscream:317043778823389184>",
					msg.Author.ID))
			helpers.Relax(err)
			return
		}
	}

	_, err = session.ChannelMessageSend(msg.ChannelID, fmt.Sprintf("<@%s> Your streamable is done: %s .", msg.Author.ID, streamableUrl))
	helpers.Relax(err)
}
<|endoftext|>


<commit_before>// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
	"net"
	"net/url"
	"regexp"
	"strings"

	"code.gitea.io/gitea/modules/setting"
)

var loopbackIPBlocks []*net.IPNet

var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`)

func init() {
	for _, cidr := range []string{
		"127.0.0.0/8", // IPv4 loopback
		"::1/128",     // IPv6 loopback
	} {
		if _, block, err := net.ParseCIDR(cidr); err == nil {
			loopbackIPBlocks = append(loopbackIPBlocks, block)
		}
	}
}

func isLoopbackIP(ip string) bool {
	pip := net.ParseIP(ip)
	if pip == nil {
		return false
	}
	for _, block := range loopbackIPBlocks {
		if block.Contains(pip) {
			return true
		}
	}
	return false
}

// IsValidURL checks if URL is valid
func IsValidURL(uri string) bool {
	if u, err := url.ParseRequestURI(uri); err != nil ||
		(u.Scheme != "http" && u.Scheme != "https") ||
		!validPort(portOnly(u.Host)) {
		return false
	}

	return true
}

// IsValidSiteURL checks if URL is valid
func IsValidSiteURL(uri string) bool {
	u, err := url.ParseRequestURI(uri)
	if err != nil {
		return false
	}

	if !validPort(portOnly(u.Host)) {
		return false
	}

	for _, scheme := range setting.Service.ValidSiteURLSchemes {
		if scheme == u.Scheme {
			return true
		}
	}
	return false
}

// IsAPIURL checks if URL is current Gitea instance API URL
func IsAPIURL(uri string) bool {
	return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
}

// IsValidExternalURL checks if URL is valid external URL
func IsValidExternalURL(uri string) bool {
	if !IsValidURL(uri) || IsAPIURL(uri) {
		return false
	}

	u, err := url.ParseRequestURI(uri)
	if err != nil {
		return false
	}

	// Currently check only if not loopback IP is provided to keep compatibility
	if isLoopbackIP(u.Hostname()) || strings.ToLower(u.Hostname()) == "localhost" {
		return false
	}

	// TODO: Later it should be added to allow local network IP addresses
	//       only if allowed by special setting

	return true
}

// IsValidExternalTrackerURLFormat checks if URL matches required syntax for external trackers
func IsValidExternalTrackerURLFormat(uri string) bool {
	if !IsValidExternalURL(uri) {
		return false
	}

	// check for typoed variables like /{index/ or /[repo}
	for _, match := range externalTrackerRegex.FindAllStringSubmatch(uri, -1) {
		if (match[1] == "{" || match[2] == "}") && (match[1] != "{" || match[2] != "}") {
			return false
		}
	}

	return true
}
<commit_msg>use IsLoopback (#19477)<commit_after>// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package validation

import (
	"net"
	"net/url"
	"regexp"
	"strings"

	"code.gitea.io/gitea/modules/setting"
)

var externalTrackerRegex = regexp.MustCompile(`({?)(?:user|repo|index)+?(}?)`)

func isLoopbackIP(ip string) bool {
	return net.ParseIP(ip).IsLoopback()
}

// IsValidURL checks if URL is valid
func IsValidURL(uri string) bool {
	if u, err := url.ParseRequestURI(uri); err != nil ||
		(u.Scheme != "http" && u.Scheme != "https") ||
		!validPort(portOnly(u.Host)) {
		return false
	}

	return true
}

// IsValidSiteURL checks if URL is valid
func IsValidSiteURL(uri string) bool {
	u, err := url.ParseRequestURI(uri)
	if err != nil {
		return false
	}

	if !validPort(portOnly(u.Host)) {
		return false
	}

	for _, scheme := range setting.Service.ValidSiteURLSchemes {
		if scheme == u.Scheme {
			return true
		}
	}
	return false
}

// IsAPIURL checks if URL is current Gitea instance API URL
func IsAPIURL(uri string) bool {
	return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
}

// IsValidExternalURL checks if URL is valid external URL
func IsValidExternalURL(uri string) bool {
	if !IsValidURL(uri) || IsAPIURL(uri) {
		return false
	}

	u, err := url.ParseRequestURI(uri)
	if err != nil {
		return false
	}

	// Currently check only if not loopback IP is provided to keep compatibility
	if isLoopbackIP(u.Hostname()) || strings.ToLower(u.Hostname()) == "localhost" {
		return false
	}

	// TODO: Later it should be added to allow local network IP addresses
	//       only if allowed by special setting

	return true
}

// IsValidExternalTrackerURLFormat checks if URL matches required syntax for external trackers
func IsValidExternalTrackerURLFormat(uri string) bool {
	if !IsValidExternalURL(uri) {
		return false
	}

	// check for typoed variables like /{index/ or /[repo}
	for _, match := range externalTrackerRegex.FindAllStringSubmatch(uri, -1) {
		if (match[1] == "{" || match[2] == "}") && (match[1] != "{" || match[2] != "}") {
			return false
		}
	}

	return true
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package framework

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
)

func EtcdUpgrade(target_storage, target_version string) error {
	switch TestContext.Provider {
	case "gce":
		return etcdUpgradeGCE(target_storage, target_version)
	default:
		return fmt.Errorf("EtcdUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
}

func IngressUpgrade(isUpgrade bool) error {
	switch TestContext.Provider {
	case "gce":
		return ingressUpgradeGCE(isUpgrade)
	default:
		return fmt.Errorf("IngressUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
}

func MasterUpgrade(v string) error {
	switch TestContext.Provider {
	case "gce":
		return masterUpgradeGCE(v, false)
	case "gke":
		return masterUpgradeGKE(v)
	case "kubernetes-anywhere":
		return masterUpgradeKubernetesAnywhere(v)
	default:
		return fmt.Errorf("MasterUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
}

func etcdUpgradeGCE(target_storage, target_version string) error {
	env := append(
		os.Environ(),
		"TEST_ETCD_VERSION="+target_version,
		"STORAGE_BACKEND="+target_storage,
		"TEST_ETCD_IMAGE=3.2.18-0")

	_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-l", "-M")
	return err
}

func ingressUpgradeGCE(isUpgrade bool) error {
	var command string
	if isUpgrade {
		// User specified image to upgrade to.
		targetImage := TestContext.IngressUpgradeImage
		if targetImage != "" {
			command = fmt.Sprintf("sudo sed -i -re 's|(image:)(.*)|\\1 %s|' /etc/kubernetes/manifests/glbc.manifest", targetImage)
		} else {
			// Upgrade to latest HEAD image.
			command = "sudo sed -i -re 's/(image:)(.*)/\\1 gcr.io\\/k8s-ingress-image-push\\/ingress-gce-e2e-glbc-amd64:master/' /etc/kubernetes/manifests/glbc.manifest"
		}
	} else {
		// Downgrade to latest release image.
		command = "sudo sed -i -re 's/(image:)(.*)/\\1 k8s.gcr.io\\/ingress-gce-glbc-amd64:v1.1.1/' /etc/kubernetes/manifests/glbc.manifest"
	}
	// Kubelet should restart glbc automatically.
	sshResult, err := NodeExec(GetMasterHost(), command)
	LogSSHResult(sshResult)
	return err
}

// TODO(mrhohn): Remove this function when kube-proxy is run as a DaemonSet by default.
func MasterUpgradeGCEWithKubeProxyDaemonSet(v string, enableKubeProxyDaemonSet bool) error {
	return masterUpgradeGCE(v, enableKubeProxyDaemonSet)
}

// TODO(mrhohn): Remove 'enableKubeProxyDaemonSet' when kube-proxy is run as a DaemonSet by default.
func masterUpgradeGCE(rawV string, enableKubeProxyDaemonSet bool) error {
	env := append(os.Environ(), fmt.Sprintf("KUBE_PROXY_DAEMONSET=%v", enableKubeProxyDaemonSet))
	// TODO: Remove these variables when they're no longer needed for downgrades.
	if TestContext.EtcdUpgradeVersion != "" && TestContext.EtcdUpgradeStorage != "" {
		env = append(env,
			"TEST_ETCD_VERSION="+TestContext.EtcdUpgradeVersion,
			"STORAGE_BACKEND="+TestContext.EtcdUpgradeStorage,
			"TEST_ETCD_IMAGE=3.2.18-0")
	} else {
		// In e2e tests, we skip the confirmation prompt about
		// implicit etcd upgrades to simulate the user entering "y".
		env = append(env, "TEST_ALLOW_IMPLICIT_ETCD_UPGRADE=true")
	}

	v := "v" + rawV
	_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-M", v)
	return err
}

func locationParamGKE() string {
	if TestContext.CloudConfig.MultiMaster {
		// GKE Regional Clusters are being tested.
		return fmt.Sprintf("--region=%s", TestContext.CloudConfig.Region)
	}
	return fmt.Sprintf("--zone=%s", TestContext.CloudConfig.Zone)
}

func appendContainerCommandGroupIfNeeded(args []string) []string {
	if TestContext.CloudConfig.Region != "" {
		// TODO(wojtek-t): Get rid of it once Regional Clusters go to GA.
		return append([]string{"beta"}, args...)
	}
	return args
}

func masterUpgradeGKE(v string) error {
	Logf("Upgrading master to %q", v)
	args := []string{
		"container",
		"clusters",
		fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
		locationParamGKE(),
		"upgrade",
		TestContext.CloudConfig.Cluster,
		"--master",
		fmt.Sprintf("--cluster-version=%s", v),
		"--quiet",
	}
	_, _, err := RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)
	if err != nil {
		return err
	}

	waitForSSHTunnels()

	return nil
}

func masterUpgradeKubernetesAnywhere(v string) error {
	Logf("Upgrading master to %q", v)

	kaPath := TestContext.KubernetesAnywherePath
	originalConfigPath := filepath.Join(kaPath, ".config")
	backupConfigPath := filepath.Join(kaPath, ".config.bak")
	updatedConfigPath := filepath.Join(kaPath, fmt.Sprintf(".config-%s", v))

	// modify config with specified k8s version
	if _, _, err := RunCmd("sed",
		"-i.bak", // writes original to .config.bak
		fmt.Sprintf(`s/kubernetes_version=.*$/kubernetes_version=%q/`, v),
		originalConfigPath); err != nil {
		return err
	}

	defer func() {
		// revert .config.bak to .config
		if err := os.Rename(backupConfigPath, originalConfigPath); err != nil {
			Logf("Could not rename %s back to %s", backupConfigPath, originalConfigPath)
		}
	}()

	// invoke ka upgrade
	if _, _, err := RunCmd("make", "-C", TestContext.KubernetesAnywherePath,
		"WAIT_FOR_KUBECONFIG=y", "upgrade-master"); err != nil {
		return err
	}

	// move .config to .config.<version>
	if err := os.Rename(originalConfigPath, updatedConfigPath); err != nil {
		return err
	}

	return nil
}

func NodeUpgrade(f *Framework, v string, img string) error {
	// Perform the upgrade.
	var err error
	switch TestContext.Provider {
	case "gce":
		err = nodeUpgradeGCE(v, img, false)
	case "gke":
		err = nodeUpgradeGKE(v, img)
	default:
		err = fmt.Errorf("NodeUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
	if err != nil {
		return err
	}

	// Wait for it to complete and validate nodes are healthy.
	//
	// TODO(ihmccreery) We shouldn't have to wait for nodes to be ready in
	// GKE; the operation shouldn't return until they all are.
	Logf("Waiting up to %v for all nodes to be ready after the upgrade", RestartNodeReadyAgainTimeout)
	if _, err := CheckNodesReady(f.ClientSet, TestContext.CloudConfig.NumNodes, RestartNodeReadyAgainTimeout); err != nil {
		return err
	}
	return nil
}

// TODO(mrhohn): Remove this function when kube-proxy is run as a DaemonSet by default.
func NodeUpgradeGCEWithKubeProxyDaemonSet(f *Framework, v string, img string, enableKubeProxyDaemonSet bool) error {
	// Perform the upgrade.
	if err := nodeUpgradeGCE(v, img, enableKubeProxyDaemonSet); err != nil {
		return err
	}
	// Wait for it to complete and validate nodes are healthy.
	Logf("Waiting up to %v for all nodes to be ready after the upgrade", RestartNodeReadyAgainTimeout)
	if _, err := CheckNodesReady(f.ClientSet, TestContext.CloudConfig.NumNodes, RestartNodeReadyAgainTimeout); err != nil {
		return err
	}
	return nil
}

// TODO(mrhohn): Remove 'enableKubeProxyDaemonSet' when kube-proxy is run as a DaemonSet by default.
func nodeUpgradeGCE(rawV, img string, enableKubeProxyDaemonSet bool) error {
	v := "v" + rawV
	env := append(os.Environ(), fmt.Sprintf("KUBE_PROXY_DAEMONSET=%v", enableKubeProxyDaemonSet))
	if img != "" {
		env = append(env, "KUBE_NODE_OS_DISTRIBUTION="+img)
		_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-N", "-o", v)
		return err
	}
	_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-N", v)
	return err
}

func nodeUpgradeGKE(v string, img string) error {
	Logf("Upgrading nodes to version %q and image %q", v, img)
	args := []string{
		"container",
		"clusters",
		fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
		locationParamGKE(),
		"upgrade",
		TestContext.CloudConfig.Cluster,
		fmt.Sprintf("--cluster-version=%s", v),
		"--quiet",
	}
	if len(img) > 0 {
		args = append(args, fmt.Sprintf("--image-type=%s", img))
	}
	_, _, err := RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)

	if err != nil {
		return err
	}

	waitForSSHTunnels()

	return nil
}

// MigTemplate (GCE-only) returns the name of the MIG template that the
// nodes of the cluster use.
func MigTemplate() (string, error) {
	var errLast error
	var templ string
	key := "instanceTemplate"
	if wait.Poll(Poll, SingleCallTimeout, func() (bool, error) {
		// TODO(mikedanese): make this hit the compute API directly instead of
		// shelling out to gcloud.
		// An `instance-groups managed describe` call outputs what we want to stdout.
		output, _, err := retryCmd("gcloud", "compute", "instance-groups", "managed",
			fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
			"describe",
			fmt.Sprintf("--zone=%s", TestContext.CloudConfig.Zone),
			TestContext.CloudConfig.NodeInstanceGroup)
		if err != nil {
			errLast = fmt.Errorf("gcloud compute instance-groups managed describe call failed with err: %v", err)
			return false, nil
		}

		// The 'describe' call probably succeeded; parse the output and try to
		// find the line that looks like "instanceTemplate: url/to/<templ>" and
		// return <templ>.
		if val := ParseKVLines(output, key); len(val) > 0 {
			url := strings.Split(val, "/")
			templ = url[len(url)-1]
			Logf("MIG group %s using template: %s", TestContext.CloudConfig.NodeInstanceGroup, templ)
			return true, nil
		}
		errLast = fmt.Errorf("couldn't find %s in output to get MIG template. Output: %s", key, output)
		return false, nil
	}) != nil {
		return "", fmt.Errorf("MigTemplate() failed with last error: %v", errLast)
	}
	return templ, nil
}

func gceUpgradeScript() string {
	if len(TestContext.GCEUpgradeScript) == 0 {
		return path.Join(TestContext.RepoRoot, "cluster/gce/upgrade.sh")
	}
	return TestContext.GCEUpgradeScript
}

func waitForSSHTunnels() {
	Logf("Waiting for SSH tunnels to establish")
	RunKubectl("run", "ssh-tunnel-test",
		"--image=busybox",
		"--restart=Never",
		"--command", "--",
		"echo", "Hello")
	defer RunKubectl("delete", "pod", "ssh-tunnel-test")

	// allow up to a minute for new ssh tunnels to establish
	wait.PollImmediate(5*time.Second, time.Minute, func() (bool, error) {
		_, err := RunKubectl("logs", "ssh-tunnel-test")
		return err == nil, nil
	})
}
<commit_msg>Fix GKE Regional Clusters upgrade tests<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package framework

import (
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
)

func EtcdUpgrade(target_storage, target_version string) error {
	switch TestContext.Provider {
	case "gce":
		return etcdUpgradeGCE(target_storage, target_version)
	default:
		return fmt.Errorf("EtcdUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
}

func IngressUpgrade(isUpgrade bool) error {
	switch TestContext.Provider {
	case "gce":
		return ingressUpgradeGCE(isUpgrade)
	default:
		return fmt.Errorf("IngressUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
}

func MasterUpgrade(v string) error {
	switch TestContext.Provider {
	case "gce":
		return masterUpgradeGCE(v, false)
	case "gke":
		return masterUpgradeGKE(v)
	case "kubernetes-anywhere":
		return masterUpgradeKubernetesAnywhere(v)
	default:
		return fmt.Errorf("MasterUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
}

func etcdUpgradeGCE(target_storage, target_version string) error {
	env := append(
		os.Environ(),
		"TEST_ETCD_VERSION="+target_version,
		"STORAGE_BACKEND="+target_storage,
		"TEST_ETCD_IMAGE=3.2.18-0")

	_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-l", "-M")
	return err
}

func ingressUpgradeGCE(isUpgrade bool) error {
	var command string
	if isUpgrade {
		// User specified image to upgrade to.
		targetImage := TestContext.IngressUpgradeImage
		if targetImage != "" {
			command = fmt.Sprintf("sudo sed -i -re 's|(image:)(.*)|\\1 %s|' /etc/kubernetes/manifests/glbc.manifest", targetImage)
		} else {
			// Upgrade to latest HEAD image.
			command = "sudo sed -i -re 's/(image:)(.*)/\\1 gcr.io\\/k8s-ingress-image-push\\/ingress-gce-e2e-glbc-amd64:master/' /etc/kubernetes/manifests/glbc.manifest"
		}
	} else {
		// Downgrade to latest release image.
		command = "sudo sed -i -re 's/(image:)(.*)/\\1 k8s.gcr.io\\/ingress-gce-glbc-amd64:v1.1.1/' /etc/kubernetes/manifests/glbc.manifest"
	}
	// Kubelet should restart glbc automatically.
	sshResult, err := NodeExec(GetMasterHost(), command)
	LogSSHResult(sshResult)
	return err
}

// TODO(mrhohn): Remove this function when kube-proxy is run as a DaemonSet by default.
func MasterUpgradeGCEWithKubeProxyDaemonSet(v string, enableKubeProxyDaemonSet bool) error {
	return masterUpgradeGCE(v, enableKubeProxyDaemonSet)
}

// TODO(mrhohn): Remove 'enableKubeProxyDaemonSet' when kube-proxy is run as a DaemonSet by default.
func masterUpgradeGCE(rawV string, enableKubeProxyDaemonSet bool) error {
	env := append(os.Environ(), fmt.Sprintf("KUBE_PROXY_DAEMONSET=%v", enableKubeProxyDaemonSet))
	// TODO: Remove these variables when they're no longer needed for downgrades.
	if TestContext.EtcdUpgradeVersion != "" && TestContext.EtcdUpgradeStorage != "" {
		env = append(env,
			"TEST_ETCD_VERSION="+TestContext.EtcdUpgradeVersion,
			"STORAGE_BACKEND="+TestContext.EtcdUpgradeStorage,
			"TEST_ETCD_IMAGE=3.2.18-0")
	} else {
		// In e2e tests, we skip the confirmation prompt about
		// implicit etcd upgrades to simulate the user entering "y".
		env = append(env, "TEST_ALLOW_IMPLICIT_ETCD_UPGRADE=true")
	}

	v := "v" + rawV
	_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-M", v)
	return err
}

func locationParamGKE() string {
	if TestContext.CloudConfig.MultiMaster {
		// GKE Regional Clusters are being tested.
		return fmt.Sprintf("--region=%s", TestContext.CloudConfig.Region)
	}
	return fmt.Sprintf("--zone=%s", TestContext.CloudConfig.Zone)
}

func appendContainerCommandGroupIfNeeded(args []string) []string {
	if TestContext.CloudConfig.Region != "" {
		// TODO(wojtek-t): Get rid of it once Regional Clusters go to GA.
		return append([]string{"beta"}, args...)
	}
	return args
}

func masterUpgradeGKE(v string) error {
	Logf("Upgrading master to %q", v)
	args := []string{
		"container",
		"clusters",
		fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
		locationParamGKE(),
		"upgrade",
		TestContext.CloudConfig.Cluster,
		"--master",
		fmt.Sprintf("--cluster-version=%s", v),
		"--quiet",
	}
	_, _, err := RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)
	if err != nil {
		return err
	}

	waitForSSHTunnels()

	return nil
}

func masterUpgradeKubernetesAnywhere(v string) error {
	Logf("Upgrading master to %q", v)

	kaPath := TestContext.KubernetesAnywherePath
	originalConfigPath := filepath.Join(kaPath, ".config")
	backupConfigPath := filepath.Join(kaPath, ".config.bak")
	updatedConfigPath := filepath.Join(kaPath, fmt.Sprintf(".config-%s", v))

	// modify config with specified k8s version
	if _, _, err := RunCmd("sed",
		"-i.bak", // writes original to .config.bak
		fmt.Sprintf(`s/kubernetes_version=.*$/kubernetes_version=%q/`, v),
		originalConfigPath); err != nil {
		return err
	}

	defer func() {
		// revert .config.bak to .config
		if err := os.Rename(backupConfigPath, originalConfigPath); err != nil {
			Logf("Could not rename %s back to %s", backupConfigPath, originalConfigPath)
		}
	}()

	// invoke ka upgrade
	if _, _, err := RunCmd("make", "-C", TestContext.KubernetesAnywherePath,
		"WAIT_FOR_KUBECONFIG=y", "upgrade-master"); err != nil {
		return err
	}

	// move .config to .config.<version>
	if err := os.Rename(originalConfigPath, updatedConfigPath); err != nil {
		return err
	}

	return nil
}

func NodeUpgrade(f *Framework, v string, img string) error {
	// Perform the upgrade.
	var err error
	switch TestContext.Provider {
	case "gce":
		err = nodeUpgradeGCE(v, img, false)
	case "gke":
		err = nodeUpgradeGKE(v, img)
	default:
		err = fmt.Errorf("NodeUpgrade() is not implemented for provider %s", TestContext.Provider)
	}
	if err != nil {
		return err
	}
	return waitForNodesReadyAfterUpgrade(f)
}

// TODO(mrhohn): Remove this function when kube-proxy is run as a DaemonSet by default.
func NodeUpgradeGCEWithKubeProxyDaemonSet(f *Framework, v string, img string, enableKubeProxyDaemonSet bool) error {
	// Perform the upgrade.
	if err := nodeUpgradeGCE(v, img, enableKubeProxyDaemonSet); err != nil {
		return err
	}
	return waitForNodesReadyAfterUpgrade(f)
}

func waitForNodesReadyAfterUpgrade(f *Framework) error {
	// Wait for it to complete and validate nodes are healthy.
	//
	// TODO(ihmccreery) We shouldn't have to wait for nodes to be ready in
	// GKE; the operation shouldn't return until they all are.
	numNodes, err := NumberOfRegisteredNodes(f.ClientSet)
	if err != nil {
		return fmt.Errorf("couldn't detect number of nodes")
	}
	Logf("Waiting up to %v for all %d nodes to be ready after the upgrade", RestartNodeReadyAgainTimeout, numNodes)
	if _, err := CheckNodesReady(f.ClientSet, numNodes, RestartNodeReadyAgainTimeout); err != nil {
		return err
	}
	return nil
}

// TODO(mrhohn): Remove 'enableKubeProxyDaemonSet' when kube-proxy is run as a DaemonSet by default.
func nodeUpgradeGCE(rawV, img string, enableKubeProxyDaemonSet bool) error {
	v := "v" + rawV
	env := append(os.Environ(), fmt.Sprintf("KUBE_PROXY_DAEMONSET=%v", enableKubeProxyDaemonSet))
	if img != "" {
		env = append(env, "KUBE_NODE_OS_DISTRIBUTION="+img)
		_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-N", "-o", v)
		return err
	}
	_, _, err := RunCmdEnv(env, gceUpgradeScript(), "-N", v)
	return err
}

func nodeUpgradeGKE(v string, img string) error {
	Logf("Upgrading nodes to version %q and image %q", v, img)
	args := []string{
		"container",
		"clusters",
		fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
		locationParamGKE(),
		"upgrade",
		TestContext.CloudConfig.Cluster,
		fmt.Sprintf("--cluster-version=%s", v),
		"--quiet",
	}
	if len(img) > 0 {
		args = append(args, fmt.Sprintf("--image-type=%s", img))
	}
	_, _, err := RunCmd("gcloud", appendContainerCommandGroupIfNeeded(args)...)

	if err != nil {
		return err
	}

	waitForSSHTunnels()

	return nil
}

// MigTemplate (GCE-only) returns the name of the MIG template that the
// nodes of the cluster use.
func MigTemplate() (string, error) {
	var errLast error
	var templ string
	key := "instanceTemplate"
	if wait.Poll(Poll, SingleCallTimeout, func() (bool, error) {
		// TODO(mikedanese): make this hit the compute API directly instead of
		// shelling out to gcloud.
		// An `instance-groups managed describe` call outputs what we want to stdout.
		output, _, err := retryCmd("gcloud", "compute", "instance-groups", "managed",
			fmt.Sprintf("--project=%s", TestContext.CloudConfig.ProjectID),
			"describe",
			fmt.Sprintf("--zone=%s", TestContext.CloudConfig.Zone),
			TestContext.CloudConfig.NodeInstanceGroup)
		if err != nil {
			errLast = fmt.Errorf("gcloud compute instance-groups managed describe call failed with err: %v", err)
			return false, nil
		}

		// The 'describe' call probably succeeded; parse the output and try to
		// find the line that looks like "instanceTemplate: url/to/<templ>" and
		// return <templ>.
		if val := ParseKVLines(output, key); len(val) > 0 {
			url := strings.Split(val, "/")
			templ = url[len(url)-1]
			Logf("MIG group %s using template: %s", TestContext.CloudConfig.NodeInstanceGroup, templ)
			return true, nil
		}
		errLast = fmt.Errorf("couldn't find %s in output to get MIG template. Output: %s", key, output)
		return false, nil
	}) != nil {
		return "", fmt.Errorf("MigTemplate() failed with last error: %v", errLast)
	}
	return templ, nil
}

func gceUpgradeScript() string {
	if len(TestContext.GCEUpgradeScript) == 0 {
		return path.Join(TestContext.RepoRoot, "cluster/gce/upgrade.sh")
	}
	return TestContext.GCEUpgradeScript
}

func waitForSSHTunnels() {
	Logf("Waiting for SSH tunnels to establish")
	RunKubectl("run", "ssh-tunnel-test",
		"--image=busybox",
		"--restart=Never",
		"--command", "--",
		"echo", "Hello")
	defer RunKubectl("delete", "pod", "ssh-tunnel-test")

	// allow up to a minute for new ssh tunnels to establish
	wait.PollImmediate(5*time.Second, time.Minute, func() (bool, error) {
		_, err := RunKubectl("logs", "ssh-tunnel-test")
		return err == nil, nil
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/foize/go.sgr"
	"github.com/howeyc/fsnotify"
	"go/build"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
)

var (
	wd string // working directory

	pkg *build.Package

	monAngo *exec.Cmd // command builds ango on file change (uses gomon)

	// watcher on the ango binary
	watcher *fsnotify.Watcher

	// closed on stop
	stopCh = make(chan bool)
	stopWg sync.WaitGroup
)

type CheckWriter struct {
	wr     io.Writer
	filter string
	action func()
}

func (c *CheckWriter) Write(b []byte) (n int, err error) {
	n, err = c.wr.Write(b)
	if strings.Contains(string(b), c.filter) {
		c.action()
	}
	return n, err
}
func main() {
	var err error
	fmt.Println("Starting ango dev tool.")

	wd, err = os.Getwd()
	if err != nil {
		fmt.Printf("Error getting wd: %s\n", err)
		stop(1)
		select {}
	}

	pkg, err = build.ImportDir(wd, 0)
	if err != nil {
		fmt.Printf("Error loading package: %s\n", err)
		stop(1)
		select {}
	}

	if pkg.Name != "main" || !pkg.IsCommand() || filepath.Base(wd) != "ango" {
		fmt.Println("Is tool executed from the right directory? (github.com/GeertJohan/ango or a fork)?")
		fmt.Printf("Current package (%s) is invalid.\n", pkg.Name)
		stop(1)
		select {}
	}

	go rerunExample()

	go rerunAngo()

	go watchExampleAngo()

	sigChan := make(chan os.Signal)
	signal.Notify(sigChan, os.Kill, os.Interrupt)
	sig := <-sigChan
	signal.Stop(sigChan)

	fmt.Printf("Received %s, closing...\n", sig)
	if sig == os.Interrupt {
		stop(0)
	} else {
		stop(1)
	}
	select {}
}

func stop(exitCode int) {
	go func() {
		// synced stop
		close(stopCh)
		stopWg.Wait()

		// os.Exit(..)
		os.Exit(exitCode)
	}()
}

func rerunExample() {
	stopWg.Add(1)
	defer stopWg.Done()

	cmdRerun := exec.Command("rerun", filepath.Join(pkg.ImportPath, "example"))
	cmdRerun.Stdin = os.Stdin
	cmdRerun.Stdout = sgr.NewColorWriter(os.Stdout, sgr.FgYellow, false)
	cmdRerun.Stderr = sgr.NewColorWriter(os.Stderr, sgr.FgYellow, false)
	err := cmdRerun.Start()
	if err != nil {
		fmt.Printf("Error running rerun example: %s\n", err)
		stop(1)
		return
	}
	<-stopCh
	if cmdRerun.Process != nil {
		cmdRerun.Process.Signal(os.Interrupt)
	}
	err = cmdRerun.Wait()
	if err != nil && err.Error() != "exit status 2" {
		fmt.Printf("Error stopping rerun example: %s\n", err)
		return
	}
}

func rerunAngo() {
	stopWg.Add(1)
	defer stopWg.Done()

	cmdRerun := exec.Command("rerun", "-build-only", pkg.ImportPath)
	cmdRerun.Stdin = os.Stdin
	cw := &CheckWriter{
		wr:     os.Stderr,
		filter: "build passed",
		action: func() {
			angoExample()
		},
	}
	cmdRerun.Stdout = sgr.NewColorWriter(os.Stdout, sgr.FgCyan, false)
	cmdRerun.Stderr = sgr.NewColorWriter(cw, sgr.FgCyan, false)
	err := cmdRerun.Start()
	if err != nil {
		fmt.Printf("Error running rerun ango build: %s\n", err)
		stop(1)
		return
	}
	<-stopCh
	if cmdRerun.Process != nil {
		cmdRerun.Process.Signal(os.Interrupt)
	}
	err = cmdRerun.Wait()
	if err != nil && err.Error() != "exit status 2" {
		fmt.Printf("Error stopping rerun ango build: %s\n", err)
		return
	}
}

func watchExampleAngo() {
	stopWg.Add(1)
	defer stopWg.Done()

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Printf("Error starting watcher: %s\n", err)
		stop(1)
		return
	}
	defer watcher.Close()
	<-stopCh
}

func angoExample() {
	stopWg.Add(1)
	defer stopWg.Done()
	fmt.Println("Running ango tool for example/chatService.ango")
	cmdAngoExample := exec.Command(filepath.Join(wd, "ango"), "--verbose", "-i", "example/chatService.ango", "--js", "example/http-files", "--force-overwrite") // "--go", "example",
	cmdAngoExample.Stdin = os.Stdin
	cmdAngoExample.Stdout = sgr.NewColorWriter(os.Stdout, sgr.FgBlue, false)
	cmdAngoExample.Stderr = sgr.NewColorWriter(os.Stderr, sgr.FgBlue, false)
	err := cmdAngoExample.Run()
	if err != nil {
		fmt.Printf("Error running ango tool: %s\n", err)
	}
}
<commit_msg>Add example .ango file watcher<commit_after>package main

import (
	"fmt"
	"github.com/foize/go.sgr"
	"github.com/howeyc/fsnotify"
	"go/build"
	"io"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"sync"
)

var (
	wd string // working directory

	pkg *build.Package

	monAngo *exec.Cmd // command builds ango on file change (uses gomon)

	// watcher on the ango binary
	watcher *fsnotify.Watcher

	// closed on stop
	stopCh = make(chan bool)
	stopWg sync.WaitGroup
)

const exampleAngoFile = "example/chatService.ango"

type CheckWriter struct {
	wr     io.Writer
	filter string
	action func()
}

func (c *CheckWriter) Write(b []byte) (n int, err error) {
	n, err = c.wr.Write(b)
	if strings.Contains(string(b), c.filter) {
		c.action()
	}
	return n, err
}
func main() {
	var err error
	fmt.Println("Starting ango dev tool.")

	wd, err = os.Getwd()
	if err != nil {
		fmt.Printf("Error getting wd: %s\n", err)
		stop(1)
		select {}
	}

	pkg, err = build.ImportDir(wd, 0)
	if err != nil {
		fmt.Printf("Error loading package: %s\n", err)
		stop(1)
		select {}
	}

	if pkg.Name != "main" || !pkg.IsCommand() || filepath.Base(wd) != "ango" {
		fmt.Println("Is tool executed from the right directory? (github.com/GeertJohan/ango or a fork)?")
		fmt.Printf("Current package (%s) is invalid.\n", pkg.Name)
		stop(1)
		select {}
	}

	go rerunExample()

	go rerunAngo()

	go watchExampleAngo()

	sigChan := make(chan os.Signal)
	signal.Notify(sigChan, os.Kill, os.Interrupt)
	sig := <-sigChan
	signal.Stop(sigChan)

	fmt.Printf("Received %s, closing...\n", sig)
	if sig == os.Interrupt {
		stop(0)
	} else {
		stop(1)
	}
	select {}
}

func stop(exitCode int) {
	go func() {
		// synced stop
		close(stopCh)
		stopWg.Wait()

		// os.Exit(..)
		os.Exit(exitCode)
	}()
}

func rerunExample() {
	stopWg.Add(1)
	defer stopWg.Done()

	cmdRerun := exec.Command("rerun", filepath.Join(pkg.ImportPath, "example"))
	cmdRerun.Stdin = os.Stdin
	cmdRerun.Stdout = sgr.NewColorWriter(os.Stdout, sgr.FgYellow, false)
	cmdRerun.Stderr = sgr.NewColorWriter(os.Stderr, sgr.FgYellow, false)
	err := cmdRerun.Start()
	if err != nil {
		fmt.Printf("Error running rerun example: %s\n", err)
		stop(1)
		return
	}
	<-stopCh
	if cmdRerun.Process != nil {
		cmdRerun.Process.Signal(os.Interrupt)
	}
	err = cmdRerun.Wait()
	if err != nil && err.Error() != "exit status 2" {
		fmt.Printf("Error stopping rerun example: %s\n", err)
		return
	}
}

func rerunAngo() {
	stopWg.Add(1)
	defer stopWg.Done()

	cmdRerun := exec.Command("rerun", "-build-only", pkg.ImportPath)
	cmdRerun.Stdin = os.Stdin
	cw := &CheckWriter{
		wr:     os.Stderr,
		filter: "build passed",
		action: func() {
			angoExample()
		},
	}
	cmdRerun.Stdout = sgr.NewColorWriter(os.Stdout, sgr.FgCyan, false)
	cmdRerun.Stderr = sgr.NewColorWriter(cw, sgr.FgCyan, false)
	err := cmdRerun.Start()
	if err != nil {
		fmt.Printf("Error running rerun ango build: %s\n", err)
		stop(1)
		return
	}
	<-stopCh
	if cmdRerun.Process != nil {
		cmdRerun.Process.Signal(os.Interrupt)
	}
	err = cmdRerun.Wait()
	if err != nil && err.Error() != "exit status 2" {
		fmt.Printf("Error stopping rerun ango build: %s\n", err)
		return
	}
}

func watchExampleAngo() {
	stopWg.Add(1)
	defer stopWg.Done()

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		fmt.Printf("Error starting watcher: %s\n", err)
		stop(1)
		return
	}
	err = watcher.WatchFlags(exampleAngoFile, fsnotify.FSN_MODIFY)
	if err != nil {
		fmt.Printf("Error starting watch on example ango file: %s\n", err)
		stop(1)
		return
	}
	defer watcher.Close()
	for {
		select {
		case <-stopCh:
			return
		case <-watcher.Event:
			angoExample()
		case err := <-watcher.Error:
			if err != nil {
				fmt.Printf("Error watching example ango file: %s\n", err)
				stop(1)
				return
			}
		}
	}
}

func angoExample() {
	stopWg.Add(1)
	defer stopWg.Done()
	fmt.Println("Running ango tool for example/chatService.ango")
	cmdAngoExample := exec.Command(filepath.Join(wd, "ango"), "--verbose", "-i", exampleAngoFile, "--js", "example/http-files", "--force-overwrite") // "--go", "example",
	cmdAngoExample.Stdin = os.Stdin
	cmdAngoExample.Stdout = sgr.NewColorWriter(os.Stdout, sgr.FgBlue, false)
	cmdAngoExample.Stderr = sgr.NewColorWriter(os.Stderr, sgr.FgBlue, false)
	err := cmdAngoExample.Run()
	if err != nil {
		fmt.Printf("Error running ango tool: %s\n", err)
	}
}
<|endoftext|>