<commit_before>/*
 * Copyright (c) 2014 Jesse van den Kieboom. 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.
 *    * Neither the name of Google Inc. nor the names of its
 *      contributors may be used to endorse or promote products derived from
 *      this software without specific prior written permission.
 *
 * 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
 * OWNER 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 (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"image/png"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/mux"
)

const DefaultGalleryLimit = 10
const MaximumGalleryLimit = 50

type NewGalleryHandler struct {
	RestishVoid
}

type UpdateGalleryHandler struct {
	RestishVoid
}

type GalleryHandler struct {
	RestishVoid
}

type TokenRequest struct {
	Email  string `json:"email"`
	Title  string `json:"title"`
	Author string `json:"author"`
}

func (g NewGalleryHandler) Post(writer http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	dec := json.NewDecoder(req.Body)

	var treq TokenRequest

	if err := dec.Decode(&treq); err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	if !strings.ContainsRune(treq.Email, '@') {
		http.Error(writer, "Invalid e-mail address", http.StatusInternalServerError)
		return
	}

	if len(treq.Title) == 0 {
		http.Error(writer, "Empty title specified", http.StatusInternalServerError)
		return
	}

	if len(treq.Author) == 0 {
		treq.Author = "Anonymous"
	}

	// Generate a new random token string
	tok, err := db.NewRequest()

	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	info := EmailInfo{
		Date:  time.Now().Format(time.RFC822),
		Title: treq.Title,
		To: EmailAddress{
			Name:    treq.Author,
			Address: treq.Email,
		},
		From: EmailAddress{
			Name:    "WebGL Playground",
			Address: "noreply+webgl@jessevdk.github.io",
		},
		Token:      tok,
		PublicHost: options.PublicHost,
	}

	b := &bytes.Buffer{}

	if err := emailer.Template.Execute(b, info); err != nil {
		db.DeleteRequest(tok)
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	emailer.Emails <- Email{
		Info:    info,
		Message: b.Bytes(),
	}

	g.RespondJSON(writer, struct{}{})
}

type UpdateGalleryRequest struct {
	Document    Document `json:"document"`
	Author      string   `json:"author"`
	License     string   `json:"license"`
	Screenshot  string   `json:"screenshot"`
	Description string   `json:"description"`
	Token       string   `json:"token"`
}

type UpdateGalleryResponse struct {
	Published GalleryItem `json:"published"`
	Document  Document    `json:"document"`
}

func (g UpdateGalleryHandler) Post(writer http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	dec := json.NewDecoder(req.Body)

	var ureq UpdateGalleryRequest

	if err := dec.Decode(&ureq); err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	if len(ureq.Token) == 0 {
		http.Error(writer, "Invalid token", http.StatusBadRequest)
		return
	}

	scprefix := "data:image/png;base64,"

	if !strings.HasPrefix(ureq.Screenshot, scprefix) {
		http.Error(writer, "Invalid screenshot, expected "+scprefix+"...", http.StatusBadRequest)
		return
	}

	screenshotData, err := base64.StdEncoding.DecodeString(ureq.Screenshot[len(scprefix):])

	if err != nil {
		http.Error(writer, fmt.Sprintf("Failed to decode screenshot: %v", err), http.StatusBadRequest)
		return
	}

	if _, err := png.DecodeConfig(bytes.NewReader(screenshotData)); err != nil {
		http.Error(writer, fmt.Sprintf("Invalid screenshot: %v", err), http.StatusBadRequest)
		return
	}

	doc := ureq.Document

	if err := doc.ValidatePublication(); err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	author := Author{
		Name:    ureq.Author,
		License: ureq.License,
		Year:    time.Now().Year(),
	}

	if err := doc.Prepare(author); err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	// Store the doc first
	hash, err := doc.Store()

	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	// Then create the actual Gallery
	item := &GalleryItem{
		Token:       ureq.Token,
		Document:    hash,
		Title:       doc.Title,
		Description: doc.Description,
		Author:      author.Name,
		License:     author.License,
	}

	if err := db.PutGallery(item, screenshotData); err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	g.RespondJSON(writer, UpdateGalleryResponse{
		Document:  doc,
		Published: *item,
	})
}

func (g GalleryHandler) Get(writer http.ResponseWriter, req *http.Request) {
	form := req.Form

	page, err := strconv.ParseInt(form.Get("page"), 10, 32)

	if err != nil {
		page = 0
	}

	limit, err := strconv.ParseInt(form.Get("limit"), 10, 32)

	if err != nil {
		limit = DefaultGalleryLimit
	}

	if limit > MaximumGalleryLimit {
		limit = MaximumGalleryLimit
	}

	sort := form.Get("sort")

	if sort != "views" {
		sort = "newest"
	}

	reversedOrder := form.Get("order") == "reverse"

	ret, err := db.Gallery(int(page), int(limit), sort, reversedOrder)

	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	g.RespondJSON(writer, ret)
}

type ViewGalleryHandler struct {
	RestishVoid
}

func (g ViewGalleryHandler) makeIpHash(ip string) string {
	hash := sha1.Sum([]byte(ip))
	hex := "0123456789abcdef"

	ret := make([]byte, 0, len(ip)*2)

	for _, b := range hash {
		ret = append(ret, hex[b>>4], hex[b&0x0f])
	}

	return string(ret)
}

func (g ViewGalleryHandler) Post(wr http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)

	parent := vars["parent"]
	id := vars["id"]

	parentNum, err := strconv.ParseInt(parent, 10, 32)

	if err != nil {
		http.Error(wr, "Invalid parent", http.StatusBadRequest)
		return
	}

	idNum, err := strconv.ParseInt(id, 10, 32)

	if err != nil {
		http.Error(wr, "Invalid id", http.StatusBadRequest)
		return
	}

	// This isn't great, but
	h := req.Header
	realIP := h.Get("X-Real-IP")

	var ip string
	i := -1

	if len(realIP) != 0 {
		ip = realIP
	} else {
		ip = req.RemoteAddr
		i = strings.LastIndex(req.RemoteAddr, ":")
	}

	if i >= 0 {
		ip = ip[:i]
	}

	iphash := g.makeIpHash(ip)
	db.GalleryView(int(parentNum), int(idNum), iphash)
}

func init() {
	router.Handle("/g", MakeHandler(GalleryHandler{}, WrapCompress|WrapCORS))
	router.Handle("/g/new", MakeHandler(NewGalleryHandler{}, WrapCORS))
	router.Handle("/g/update", MakeHandler(UpdateGalleryHandler{}, WrapCORS))
	router.Handle("/g/{parent:[0-9]+}/{id:[0-9]+}/view", MakeHandler(ViewGalleryHandler{}, WrapCORS))
}
<commit_msg>Allow protocol relative public host<commit_after>/*
 * Copyright (c) 2014 Jesse van den Kieboom. 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.
 *    * Neither the name of Google Inc. nor the names of its
 *      contributors may be used to endorse or promote products derived from
 *      this software without specific prior written permission.
 *
 * 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
 * OWNER 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 (
	"bytes"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"image/png"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/mux"
)

const DefaultGalleryLimit = 10
const MaximumGalleryLimit = 50

type NewGalleryHandler struct {
	RestishVoid
}

type UpdateGalleryHandler struct {
	RestishVoid
}

type GalleryHandler struct {
	RestishVoid
}

type TokenRequest struct {
	Email  string `json:"email"`
	Title  string `json:"title"`
	Author string `json:"author"`
}

func (g NewGalleryHandler) Post(writer http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	dec := json.NewDecoder(req.Body)

	var treq TokenRequest

	if err := dec.Decode(&treq); err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	if !strings.ContainsRune(treq.Email, '@') {
		http.Error(writer, "Invalid e-mail address", http.StatusInternalServerError)
		return
	}

	if len(treq.Title) == 0 {
		http.Error(writer, "Empty title specified", http.StatusInternalServerError)
		return
	}

	if len(treq.Author) == 0 {
		treq.Author = "Anonymous"
	}

	// Generate a new random token string
	tok, err := db.NewRequest()

	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	publicHost := options.PublicHost

	if strings.HasPrefix(publicHost, "//") {
		publicHost = req.URL.Scheme + ":" + publicHost
	}

	info := EmailInfo{
		Date:  time.Now().Format(time.RFC822),
		Title: treq.Title,
		To: EmailAddress{
			Name:    treq.Author,
			Address: treq.Email,
		},
		From: EmailAddress{
			Name:    "WebGL Playground",
			Address: "noreply+webgl@jessevdk.github.io",
		},
		Token:      tok,
		PublicHost: options.PublicHost,
	}

	b := &bytes.Buffer{}

	if err := emailer.Template.Execute(b, info); err != nil {
		db.DeleteRequest(tok)
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	emailer.Emails <- Email{
		Info:    info,
		Message: b.Bytes(),
	}

	g.RespondJSON(writer, struct{}{})
}

type UpdateGalleryRequest struct {
	Document    Document `json:"document"`
	Author      string   `json:"author"`
	License     string   `json:"license"`
	Screenshot  string   `json:"screenshot"`
	Description string   `json:"description"`
	Token       string   `json:"token"`
}

type UpdateGalleryResponse struct {
	Published GalleryItem `json:"published"`
	Document  Document    `json:"document"`
}

func (g UpdateGalleryHandler) Post(writer http.ResponseWriter, req *http.Request) {
	defer req.Body.Close()

	dec := json.NewDecoder(req.Body)

	var ureq UpdateGalleryRequest

	if err := dec.Decode(&ureq); err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	if len(ureq.Token) == 0 {
		http.Error(writer, "Invalid token", http.StatusBadRequest)
		return
	}

	scprefix := "data:image/png;base64,"

	if !strings.HasPrefix(ureq.Screenshot, scprefix) {
		http.Error(writer, "Invalid screenshot, expected "+scprefix+"...", http.StatusBadRequest)
		return
	}

	screenshotData, err := base64.StdEncoding.DecodeString(ureq.Screenshot[len(scprefix):])

	if err != nil {
		http.Error(writer, fmt.Sprintf("Failed to decode screenshot: %v", err), http.StatusBadRequest)
		return
	}

	if _, err := png.DecodeConfig(bytes.NewReader(screenshotData)); err != nil {
		http.Error(writer, fmt.Sprintf("Invalid screenshot: %v", err), http.StatusBadRequest)
		return
	}

	doc := ureq.Document

	if err := doc.ValidatePublication(); err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	author := Author{
		Name:    ureq.Author,
		License: ureq.License,
		Year:    time.Now().Year(),
	}

	if err := doc.Prepare(author); err != nil {
		http.Error(writer, err.Error(), http.StatusBadRequest)
		return
	}

	// Store the doc first
	hash, err := doc.Store()

	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	// Then create the actual Gallery
	item := &GalleryItem{
		Token:       ureq.Token,
		Document:    hash,
		Title:       doc.Title,
		Description: doc.Description,
		Author:      author.Name,
		License:     author.License,
	}

	if err := db.PutGallery(item, screenshotData); err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	g.RespondJSON(writer, UpdateGalleryResponse{
		Document:  doc,
		Published: *item,
	})
}

func (g GalleryHandler) Get(writer http.ResponseWriter, req *http.Request) {
	form := req.Form

	page, err := strconv.ParseInt(form.Get("page"), 10, 32)

	if err != nil {
		page = 0
	}

	limit, err := strconv.ParseInt(form.Get("limit"), 10, 32)

	if err != nil {
		limit = DefaultGalleryLimit
	}

	if limit > MaximumGalleryLimit {
		limit = MaximumGalleryLimit
	}

	sort := form.Get("sort")

	if sort != "views" {
		sort = "newest"
	}

	reversedOrder := form.Get("order") == "reverse"

	ret, err := db.Gallery(int(page), int(limit), sort, reversedOrder)

	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	g.RespondJSON(writer, ret)
}

type ViewGalleryHandler struct {
	RestishVoid
}

func (g ViewGalleryHandler) makeIpHash(ip string) string {
	hash := sha1.Sum([]byte(ip))
	hex := "0123456789abcdef"

	ret := make([]byte, 0, len(ip)*2)

	for _, b := range hash {
		ret = append(ret, hex[b>>4], hex[b&0x0f])
	}

	return string(ret)
}

func (g ViewGalleryHandler) Post(wr http.ResponseWriter, req *http.Request) {
	vars := mux.Vars(req)

	parent := vars["parent"]
	id := vars["id"]

	parentNum, err := strconv.ParseInt(parent, 10, 32)

	if err != nil {
		http.Error(wr, "Invalid parent", http.StatusBadRequest)
		return
	}

	idNum, err := strconv.ParseInt(id, 10, 32)

	if err != nil {
		http.Error(wr, "Invalid id", http.StatusBadRequest)
		return
	}

	// This isn't great, but
	h := req.Header
	realIP := h.Get("X-Real-IP")

	var ip string
	i := -1

	if len(realIP) != 0 {
		ip = realIP
	} else {
		ip = req.RemoteAddr
		i = strings.LastIndex(req.RemoteAddr, ":")
	}

	if i >= 0 {
		ip = ip[:i]
	}

	iphash := g.makeIpHash(ip)
	db.GalleryView(int(parentNum), int(idNum), iphash)
}

func init() {
	router.Handle("/g", MakeHandler(GalleryHandler{}, WrapCompress|WrapCORS))
	router.Handle("/g/new", MakeHandler(NewGalleryHandler{}, WrapCORS))
	router.Handle("/g/update", MakeHandler(UpdateGalleryHandler{}, WrapCORS))
	router.Handle("/g/{parent:[0-9]+}/{id:[0-9]+}/view", MakeHandler(ViewGalleryHandler{}, WrapCORS))
}
<|endoftext|>


<commit_before>package main

import "fmt"

type ServerCommand struct {
	Command               string                `json:"command"`
	VariableEndpointIndex VariableEndpointIndex `json:"endpoint"`
}

const (
	SERVER_COMMAND_VARY = "vary"
)

func NewServerCommand() *ServerCommand {
	return &ServerCommand{}
}

func (c ServerCommand) Execute(s *ServerApp) error {
	switch c.Command {
	case SERVER_COMMAND_VARY:
		s.EndpointVariationSchedule[c.VariableEndpointIndex.EndpointIndex] = EndpointVariation{
			Variation: c.VariableEndpointIndex.Variant,
			Count:     1,
		}

	default:
		return fmt.Errorf("Unknown command '%s'", c.Command)
	}

	return nil
}
<commit_msg>Trim leading slashes in vary commands to prevent confusion<commit_after>package main

import (
	"fmt"
	"strings"
)

type ServerCommand struct {
	Command               string                `json:"command"`
	VariableEndpointIndex VariableEndpointIndex `json:"endpoint"`
}

const (
	SERVER_COMMAND_VARY = "vary"
)

func NewServerCommand() *ServerCommand {
	return &ServerCommand{}
}

func (c ServerCommand) Execute(s *ServerApp) error {
	switch c.Command {
	case SERVER_COMMAND_VARY:

		// Trim the leading slash to prevent confusing collisions
		c.VariableEndpointIndex.EndpointIndex.URL = strings.TrimLeft(c.VariableEndpointIndex.EndpointIndex.URL, "/")

		s.EndpointVariationSchedule[c.VariableEndpointIndex.EndpointIndex] = EndpointVariation{
			Variation: c.VariableEndpointIndex.Variant,
			Count:     1,
		}

	default:
		return fmt.Errorf("Unknown command '%s'", c.Command)
	}

	return nil
}
<|endoftext|>


<commit_before>package service

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/url"
	"time"

	"github.com/aerokube/selenoid/session"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	"strings"
)

// Docker - docker container manager
type Docker struct {
	ServiceBase
	Environment
	session.Caps
	LogConfig *container.LogConfig
	Client    *client.Client
}

// StartWithCancel - Starter interface implementation
func (d *Docker) StartWithCancel() (*StartedService, error) {
	selenium, err := nat.NewPort("tcp", d.Service.Port)
	if err != nil {
		return nil, fmt.Errorf("new selenium port: %v", err)
	}
	exposedPorts := map[nat.Port]struct{}{selenium: {}}
	var vnc nat.Port
	if d.VNC {
		vnc, err = nat.NewPort("tcp", "5900")
		if err != nil {
			return nil, fmt.Errorf("new vnc port: %v", err)
		}
		exposedPorts[vnc] = struct{}{}
	}
	portBindings := nat.PortMap{}
	if d.IP != "" || !d.InDocker {
		portBindings[selenium] = []nat.PortBinding{{HostIP: "0.0.0.0"}}
		if d.VNC {
			portBindings[vnc] = []nat.PortBinding{{HostIP: "0.0.0.0"}}
		}
	}
	ctx := context.Background()
	log.Printf("[%d] [CREATING_CONTAINER] [%s]\n", d.RequestId, d.Service.Image)
	timeZone := time.Local
	if d.TimeZone != "" {
		tz, err := time.LoadLocation(d.TimeZone)
		if err != nil {
			log.Printf("[%d] [BAD_TIMEZONE] [%s]\n", d.RequestId, d.TimeZone)
		} else {
			timeZone = tz
		}
	}
	env := []string{
		fmt.Sprintf("TZ=%s", timeZone),
		fmt.Sprintf("SCREEN_RESOLUTION=%s", d.ScreenResolution),
		fmt.Sprintf("ENABLE_VNC=%v", d.VNC),
	}
	env = append(env, d.Service.Env...)
	shmSize := int64(268435456)
	if d.Service.ShmSize > 0 {
		shmSize = d.Service.ShmSize
	}
	extraHosts := []string{}
	if len(d.Service.Hosts) > 0 {
		extraHosts = append(extraHosts, d.Service.Hosts...)
	}
	container, err := d.Client.ContainerCreate(ctx,
		&container.Config{
			Hostname:     d.ContainerHostname,
			Image:        d.Service.Image.(string),
			Env:          env,
			ExposedPorts: exposedPorts,
		},
		&container.HostConfig{
			Binds:        d.Service.Volumes,
			AutoRemove:   true,
			PortBindings: portBindings,
			LogConfig:    *d.LogConfig,
			NetworkMode:  container.NetworkMode(d.Network),
			Tmpfs:        d.Service.Tmpfs,
			ShmSize:      shmSize,
			Privileged:   true,
			Links:        strings.Split(d.ApplicationContainers, ","),
			Resources: container.Resources{
				Memory:   d.Memory,
				NanoCPUs: d.CPU,
			},
			ExtraHosts: extraHosts,
		},
		&network.NetworkingConfig{}, "")
	if err != nil {
		return nil, fmt.Errorf("create container: %v", err)
	}
	containerStartTime := time.Now()
	log.Printf("[%d] [STARTING_CONTAINER] [%s] [%s]\n", d.RequestId, d.Service.Image, container.ID)
	err = d.Client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{})
	if err != nil {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("start container: %v", err)
	}
	log.Printf("[%d] [CONTAINER_STARTED] [%s] [%s] [%v]\n", d.RequestId, d.Service.Image, container.ID, time.Since(containerStartTime))
	stat, err := d.Client.ContainerInspect(ctx, container.ID)
	if err != nil {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("inspect container %s: %s", container.ID, err)
	}
	_, ok := stat.NetworkSettings.Ports[selenium]
	if !ok {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("no bindings available for %v", selenium)
	}
	seleniumHostPort, vncHostPort := "", ""
	if d.IP == "" {
		if d.InDocker {
			containerIP := getContainerIP(d.Network, stat)
			seleniumHostPort = net.JoinHostPort(containerIP, d.Service.Port)
			if d.VNC {
				vncHostPort = net.JoinHostPort(containerIP, "5900")
			}
		} else {
			seleniumHostPort = net.JoinHostPort("127.0.0.1", stat.NetworkSettings.Ports[selenium][0].HostPort)
			if d.VNC {
				vncHostPort = net.JoinHostPort("127.0.0.1", stat.NetworkSettings.Ports[vnc][0].HostPort)
			}
		}
	} else {
		seleniumHostPort = net.JoinHostPort(d.IP, stat.NetworkSettings.Ports[selenium][0].HostPort)
		if d.VNC {
			vncHostPort = net.JoinHostPort(d.IP, stat.NetworkSettings.Ports[vnc][0].HostPort)
		}
	}
	u := &url.URL{Scheme: "http", Host: seleniumHostPort, Path: d.Service.Path}
	serviceStartTime := time.Now()
	err = wait(u.String(), d.StartupTimeout)
	if err != nil {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("wait: %v", err)
	}
	log.Printf("[%d] [SERVICE_STARTED] [%s] [%s] [%v]\n", d.RequestId, d.Service.Image, container.ID, time.Since(serviceStartTime))
	log.Printf("[%d] [PROXY_TO] [%s] [%s] [%s]\n", d.RequestId, d.Service.Image, container.ID, u.String())
	s := StartedService{
		Url:         u,
		ID:          container.ID,
		VNCHostPort: vncHostPort,
		Cancel:      func() { d.removeContainer(ctx, d.Client, container.ID) },
	}
	return &s, nil
}

func getContainerIP(networkName string, stat types.ContainerJSON) string {
	ns := stat.NetworkSettings
	if ns.IPAddress != "" {
		return stat.NetworkSettings.IPAddress
	}
	if len(ns.Networks) > 0 {
		possibleAddresses := []string{}
		for name, nt := range ns.Networks {
			if nt.IPAddress != "" {
				if name == networkName {
					return nt.IPAddress
				}
				possibleAddresses = append(possibleAddresses, nt.IPAddress)
			}
		}
		if len(possibleAddresses) > 0 {
			return possibleAddresses[0]
		}
	}
	return ""
}

func (d *Docker) removeContainer(ctx context.Context, cli *client.Client, id string) {
	log.Printf("[%d] [REMOVE_CONTAINER] [%s]\n", d.RequestId, id)
	err := cli.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true, RemoveVolumes: true})
	if err != nil {
		log.Printf("[%d] [FAILED_TO_REMOVE_CONTAINER] [%s] [%v]\n", d.RequestId, id, err)
		return
	}
	log.Printf("[%d] [CONTAINER_REMOVED] [%s]\n", d.RequestId, id)
}
<commit_msg>Adding links only when specified (related to #217)<commit_after>package service

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/url"
	"time"

	"github.com/aerokube/selenoid/session"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
	"strings"
)

// Docker - docker container manager
type Docker struct {
	ServiceBase
	Environment
	session.Caps
	LogConfig *container.LogConfig
	Client    *client.Client
}

// StartWithCancel - Starter interface implementation
func (d *Docker) StartWithCancel() (*StartedService, error) {
	selenium, err := nat.NewPort("tcp", d.Service.Port)
	if err != nil {
		return nil, fmt.Errorf("new selenium port: %v", err)
	}
	exposedPorts := map[nat.Port]struct{}{selenium: {}}
	var vnc nat.Port
	if d.VNC {
		vnc, err = nat.NewPort("tcp", "5900")
		if err != nil {
			return nil, fmt.Errorf("new vnc port: %v", err)
		}
		exposedPorts[vnc] = struct{}{}
	}
	portBindings := nat.PortMap{}
	if d.IP != "" || !d.InDocker {
		portBindings[selenium] = []nat.PortBinding{{HostIP: "0.0.0.0"}}
		if d.VNC {
			portBindings[vnc] = []nat.PortBinding{{HostIP: "0.0.0.0"}}
		}
	}
	ctx := context.Background()
	log.Printf("[%d] [CREATING_CONTAINER] [%s]\n", d.RequestId, d.Service.Image)
	timeZone := time.Local
	if d.TimeZone != "" {
		tz, err := time.LoadLocation(d.TimeZone)
		if err != nil {
			log.Printf("[%d] [BAD_TIMEZONE] [%s]\n", d.RequestId, d.TimeZone)
		} else {
			timeZone = tz
		}
	}
	env := []string{
		fmt.Sprintf("TZ=%s", timeZone),
		fmt.Sprintf("SCREEN_RESOLUTION=%s", d.ScreenResolution),
		fmt.Sprintf("ENABLE_VNC=%v", d.VNC),
	}
	env = append(env, d.Service.Env...)
	shmSize := int64(268435456)
	if d.Service.ShmSize > 0 {
		shmSize = d.Service.ShmSize
	}
	extraHosts := []string{}
	if len(d.Service.Hosts) > 0 {
		extraHosts = append(extraHosts, d.Service.Hosts...)
	}
	containerHostname := "localhost"
	if d.ContainerHostname != "" {
		containerHostname = d.ContainerHostname
	}
	hostConfig := container.HostConfig{
		Binds:        d.Service.Volumes,
		AutoRemove:   true,
		PortBindings: portBindings,
		LogConfig:    *d.LogConfig,
		NetworkMode:  container.NetworkMode(d.Network),
		Tmpfs:        d.Service.Tmpfs,
		ShmSize:      shmSize,
		Privileged:   true,
		Resources: container.Resources{
			Memory:   d.Memory,
			NanoCPUs: d.CPU,
		},
		ExtraHosts: extraHosts,
	}
	links := strings.Split(d.ApplicationContainers, ",")
	if len(links) > 0 {
		hostConfig.Links = links
	}
	container, err := d.Client.ContainerCreate(ctx,
		&container.Config{
			Hostname:     containerHostname,
			Image:        d.Service.Image.(string),
			Env:          env,
			ExposedPorts: exposedPorts,
		},
		&hostConfig,
		&network.NetworkingConfig{}, "")
	if err != nil {
		return nil, fmt.Errorf("create container: %v", err)
	}
	containerStartTime := time.Now()
	log.Printf("[%d] [STARTING_CONTAINER] [%s] [%s]\n", d.RequestId, d.Service.Image, container.ID)
	err = d.Client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{})
	if err != nil {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("start container: %v", err)
	}
	log.Printf("[%d] [CONTAINER_STARTED] [%s] [%s] [%v]\n", d.RequestId, d.Service.Image, container.ID, time.Since(containerStartTime))
	stat, err := d.Client.ContainerInspect(ctx, container.ID)
	if err != nil {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("inspect container %s: %s", container.ID, err)
	}
	_, ok := stat.NetworkSettings.Ports[selenium]
	if !ok {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("no bindings available for %v", selenium)
	}
	seleniumHostPort, vncHostPort := "", ""
	if d.IP == "" {
		if d.InDocker {
			containerIP := getContainerIP(d.Network, stat)
			seleniumHostPort = net.JoinHostPort(containerIP, d.Service.Port)
			if d.VNC {
				vncHostPort = net.JoinHostPort(containerIP, "5900")
			}
		} else {
			seleniumHostPort = net.JoinHostPort("127.0.0.1", stat.NetworkSettings.Ports[selenium][0].HostPort)
			if d.VNC {
				vncHostPort = net.JoinHostPort("127.0.0.1", stat.NetworkSettings.Ports[vnc][0].HostPort)
			}
		}
	} else {
		seleniumHostPort = net.JoinHostPort(d.IP, stat.NetworkSettings.Ports[selenium][0].HostPort)
		if d.VNC {
			vncHostPort = net.JoinHostPort(d.IP, stat.NetworkSettings.Ports[vnc][0].HostPort)
		}
	}
	u := &url.URL{Scheme: "http", Host: seleniumHostPort, Path: d.Service.Path}
	serviceStartTime := time.Now()
	err = wait(u.String(), d.StartupTimeout)
	if err != nil {
		d.removeContainer(ctx, d.Client, container.ID)
		return nil, fmt.Errorf("wait: %v", err)
	}
	log.Printf("[%d] [SERVICE_STARTED] [%s] [%s] [%v]\n", d.RequestId, d.Service.Image, container.ID, time.Since(serviceStartTime))
	log.Printf("[%d] [PROXY_TO] [%s] [%s] [%s]\n", d.RequestId, d.Service.Image, container.ID, u.String())
	s := StartedService{
		Url:         u,
		ID:          container.ID,
		VNCHostPort: vncHostPort,
		Cancel:      func() { d.removeContainer(ctx, d.Client, container.ID) },
	}
	return &s, nil
}

func getContainerIP(networkName string, stat types.ContainerJSON) string {
	ns := stat.NetworkSettings
	if ns.IPAddress != "" {
		return stat.NetworkSettings.IPAddress
	}
	if len(ns.Networks) > 0 {
		possibleAddresses := []string{}
		for name, nt := range ns.Networks {
			if nt.IPAddress != "" {
				if name == networkName {
					return nt.IPAddress
				}
				possibleAddresses = append(possibleAddresses, nt.IPAddress)
			}
		}
		if len(possibleAddresses) > 0 {
			return possibleAddresses[0]
		}
	}
	return ""
}

func (d *Docker) removeContainer(ctx context.Context, cli *client.Client, id string) {
	log.Printf("[%d] [REMOVE_CONTAINER] [%s]\n", d.RequestId, id)
	err := cli.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true, RemoveVolumes: true})
	if err != nil {
		log.Printf("[%d] [FAILED_TO_REMOVE_CONTAINER] [%s] [%v]\n", d.RequestId, id, err)
		return
	}
	log.Printf("[%d] [CONTAINER_REMOVED] [%s]\n", d.RequestId, id)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"

	"github.com/Syfaro/telegram-bot-api"
)

// version
const VERSION = "1.0"

func main() {
	flag.Usage = func() {
		fmt.Printf("usage: %s [options] /path \"shell command\" /path2 \"shell command2\"\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(0)
	}
	version := flag.Bool("version", false, "get version")
	flag.Parse()
	if *version {
		fmt.Println(VERSION)
		os.Exit(0)
	}

	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		log.Panic(fmt.Errorf("error: need pairs of path and shell command"))
	}

	cmd_handlers := map[string]string{}

	for i := 0; i < len(args); i += 2 {
		path, cmd := args[i], args[i+1]
		if path[0] != '/' {
			log.Panic(fmt.Errorf("error: path %s dont starts with /", path))
		}
		cmd_handlers[path] = cmd
	}

	tb_token := os.Getenv("TB_TOKEN")
	if tb_token == "" {
		log.Panic("TB_TOKEN env var not found")
	}

	bot, err := tgbotapi.NewBotAPI(tb_token)
	if err != nil {
		log.Panic(err)
	}

	log.Printf("Authorized on account %s", bot.Self.UserName)

	var ucfg tgbotapi.UpdateConfig = tgbotapi.NewUpdate(0)
	ucfg.Timeout = 60
	err = bot.UpdatesChan(ucfg)

	for {
		select {
		case update := <-bot.Updates:

			chat_id := update.Message.Chat.ID

			parts := regexp.MustCompile(`\s+`).Split(update.Message.Text, 2)
			if len(parts) > 0 {
				if parts[0] == "/help" {
					reply := ""
					for cmd, shell_cmd := range cmd_handlers {
						reply += fmt.Sprintf("%s\n %s\n", cmd, shell_cmd)
					}
					msg := tgbotapi.NewMessage(chat_id, reply)
					bot.SendMessage(msg)
				}

				if cmd, found := cmd_handlers[parts[0]]; found {
					shell, params := "sh", []string{"-c", cmd}
					if len(parts) > 1 {
						params = append(params, parts[1])
					}

					os_exec_command := exec.Command(shell, params...)
					os_exec_command.Stderr = os.Stderr
					shell_out, err := os_exec_command.Output()
					if err != nil {
						log.Println("exec error: ", err)
						reply := fmt.Sprintf("exec error: %s", err)
						msg := tgbotapi.NewMessage(chat_id, reply)
						bot.SendMessage(msg)
					} else {
						reply := string(shell_out)
						msg := tgbotapi.NewMessage(chat_id, reply)
						bot.SendMessage(msg)
					}
				}
			}
		}
	}
}
<commit_msg>Refactoring<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"regexp"

	"github.com/Syfaro/telegram-bot-api"
)

// version
const VERSION = "1.0"

func main() {
	flag.Usage = func() {
		fmt.Printf("usage: %s [options] /chat_command \"shell command\" /chat_command2 \"shell command2\"\n", os.Args[0])
		flag.PrintDefaults()
		os.Exit(0)
	}
	version := flag.Bool("version", false, "get version")
	flag.Parse()
	if *version {
		fmt.Println(VERSION)
		os.Exit(0)
	}

	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		log.Fatal("error: need pairs of path and shell command")
	}

	cmd_handlers := map[string]string{}

	for i := 0; i < len(args); i += 2 {
		path, cmd := args[i], args[i+1]
		if path[0] != '/' {
			log.Fatalf("error: path %s dont starts with /", path)
		}
		cmd_handlers[path] = cmd
	}

	tb_token := os.Getenv("TB_TOKEN")
	if tb_token == "" {
		log.Fatal("TB_TOKEN env var not found")
	}

	bot, err := tgbotapi.NewBotAPI(tb_token)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Authorized on account %s", bot.Self.UserName)

	var ucfg tgbotapi.UpdateConfig = tgbotapi.NewUpdate(0)
	ucfg.Timeout = 60
	err = bot.UpdatesChan(ucfg)

	for {
		select {
		case update := <-bot.Updates:

			chat_id := update.Message.Chat.ID

			parts := regexp.MustCompile(`\s+`).Split(update.Message.Text, 2)
			if len(parts) > 0 {
				if parts[0] == "/help" {
					reply := ""
					for cmd, shell_cmd := range cmd_handlers {
						reply += fmt.Sprintf("%s\n %s\n", cmd, shell_cmd)
					}
					msg := tgbotapi.NewMessage(chat_id, reply)
					bot.SendMessage(msg)
				}

				if cmd, found := cmd_handlers[parts[0]]; found {
					shell, params := "sh", []string{"-c", cmd}
					if len(parts) > 1 {
						params = append(params, parts[1])
					}

					os_exec_command := exec.Command(shell, params...)
					os_exec_command.Stderr = os.Stderr
					shell_out, err := os_exec_command.Output()
					if err != nil {
						log.Println("exec error: ", err)
						reply := fmt.Sprintf("exec error: %s", err)
						msg := tgbotapi.NewMessage(chat_id, reply)
						bot.SendMessage(msg)
					} else {
						reply := string(shell_out)
						msg := tgbotapi.NewMessage(chat_id, reply)
						bot.SendMessage(msg)
					}
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package clang

// #include <stdlib.h>
// #include "clang-c/Index.h"
// #include "go-clang.h"
//
import "C"

// SourceLocation identifies a specific source location within a translation
// unit.
//
// Use clang_getExpansionLocation() or clang_getSpellingLocation()
// to map a source location to a particular file, line, and column.
type SourceLocation struct {
	c C.CXSourceLocation
}

// NewNullLocation creates a NULL (invalid) source location.
func NewNullLocation() SourceLocation {
	return SourceLocation{C.clang_getNullLocation()}
}

// EqualLocations determines whether two source locations, which must refer into
// the same translation unit, refer to exactly the same point in the source
// code.
// Returns non-zero if the source locations refer to the same location, zero
// if they refer to different locations.
func EqualLocations(loc1, loc2 SourceLocation) bool {
	o := C.clang_equalLocations(loc1.c, loc2.c)
	if o != C.uint(0) {
		return true
	}
	return false
}

// ExpansionLocation returns the file, line, column, and offset represented by
// the given source location.
//
// If the location refers into a macro expansion, retrieves the
// location of the macro expansion.
//
// file: if non-NULL, will be set to the file to which the given
// source location points.
//
// line: if non-NULL, will be set to the line to which the given
// source location points.
//
// column: if non-NULL, will be set to the column to which the given
// source location points.
//
// offset: if non-NULL, will be set to the offset into the
// buffer to which the given source location points.
func (l SourceLocation) ExpansionLocation() (f File, line, column, offset uint) {
	cline := C.uint(0)
	ccol := C.uint(0)
	coff := C.uint(0)
	// FIXME: undefined reference to `clang_getExpansionLocation'
	C.clang_getInstantiationLocation(l.c, &f.c, &cline, &ccol, &coff)
	line = uint(cline)
	column = uint(ccol)
	offset = uint(coff)

	return
}

/**
 * \brief Retrieve the file, line, column, and offset represented by
 * the given source location, as specified in a # line directive.
 *
 * Example: given the following source code in a file somefile.c
 *
 * #123 "dummy.c" 1
 *
 * static int func(void)
 * {
 *     return 0;
 * }
 *
 * the location information returned by this function would be
 *
 * File: dummy.c Line: 124 Column: 12
 *
 * whereas clang_getExpansionLocation would have returned
 *
 * File: somefile.c Line: 3 Column: 12
 *
 * \param location the location within a source file that will be decomposed
 * into its parts.
 *
 * \param filename [out] if non-NULL, will be set to the filename of the
 * source location. Note that filenames returned will be for "virtual" files,
 * which don't necessarily exist on the machine running clang - e.g. when
 * parsing preprocessed output obtained from a different environment. If
 * a non-NULL value is passed in, remember to dispose of the returned value
 * using \c clang_disposeString() once you've finished with it. For an invalid
 * source location, an empty string is returned.
 *
 * \param line [out] if non-NULL, will be set to the line number of the
 * source location. For an invalid source location, zero is returned.
 *
 * \param column [out] if non-NULL, will be set to the column number of the
 * source location. For an invalid source location, zero is returned.
 */
func (l SourceLocation) PresumedLocation() (fname string, line, column uint) {

	cname := cxstring{}
	defer cname.Dispose()
	cline := C.uint(0)
	ccol := C.uint(0)
	C.clang_getPresumedLocation(l.c, &cname.c, &cline, &ccol)
	fname = cname.String()
	line = uint(cline)
	column = uint(ccol)
	return
}

/**
 * \brief Legacy API to retrieve the file, line, column, and offset represented
 * by the given source location.
 *
 * This interface has been replaced by the newer interface
 * \see clang_getExpansionLocation(). See that interface's documentation for
 * details.
 */
func (l SourceLocation) InstantiationLocation() (file File, line, column, offset uint) {

	cline := C.uint(0)
	ccol := C.uint(0)
	coff := C.uint(0)
	C.clang_getInstantiationLocation(l.c,
		&file.c,
		&cline,
		&ccol,
		&coff)
	line = uint(cline)
	column = uint(ccol)
	offset = uint(coff)
	return
}

/**
 * \brief Retrieve the file, line, column, and offset represented by
 * the given source location.
 *
 * If the location refers into a macro instantiation, return where the
 * location was originally spelled in the source file.
 *
 * \param location the location within a source file that will be decomposed
 * into its parts.
 *
 * \param file [out] if non-NULL, will be set to the file to which the given
 * source location points.
 *
 * \param line [out] if non-NULL, will be set to the line to which the given
 * source location points.
 *
 * \param column [out] if non-NULL, will be set to the column to which the given
 * source location points.
 *
 * \param offset [out] if non-NULL, will be set to the offset into the
 * buffer to which the given source location points.
 */
func (l SourceLocation) SpellingLocation() (file File, line, column, offset uint) {

	cline := C.uint(0)
	ccol := C.uint(0)
	coff := C.uint(0)
	C.clang_getSpellingLocation(l.c,
		&file.c,
		&cline,
		&ccol,
		&coff)
	line = uint(cline)
	column = uint(ccol)
	offset = uint(coff)
	return
}
<commit_msg>sourcelocation: impl clang_Location_isInSystemHeader + clang_Location_isFromMainFile<commit_after>package clang

// #include <stdlib.h>
// #include "clang-c/Index.h"
// #include "go-clang.h"
//
import "C"

// SourceLocation identifies a specific source location within a translation
// unit.
//
// Use clang_getExpansionLocation() or clang_getSpellingLocation()
// to map a source location to a particular file, line, and column.
type SourceLocation struct {
	c C.CXSourceLocation
}

// NewNullLocation creates a NULL (invalid) source location.
func NewNullLocation() SourceLocation {
	return SourceLocation{C.clang_getNullLocation()}
}

// EqualLocations determines whether two source locations, which must refer into
// the same translation unit, refer to exactly the same point in the source
// code.
// Returns non-zero if the source locations refer to the same location, zero
// if they refer to different locations.
func EqualLocations(loc1, loc2 SourceLocation) bool {
	o := C.clang_equalLocations(loc1.c, loc2.c)
	if o != C.uint(0) {
		return true
	}
	return false
}

/**
 * \brief Returns non-zero if the given source location is in a system header.
 */
func (loc SourceLocation) IsInSystemHeader() bool {
	o := C.clang_Location_isInSystemHeader(loc.c)
	if o != 0 {
		return true
	}
	return false
}

/**
 * \brief Returns non-zero if the given source location is in the main file of
 * the corresponding translation unit.
 */
func (loc SourceLocation) IsFromMainFile() bool {
	o := C.clang_Location_isFromMainFile(loc.c)
	if o != 0 {
		return true
	}
	return false
}

// ExpansionLocation returns the file, line, column, and offset represented by
// the given source location.
//
// If the location refers into a macro expansion, retrieves the
// location of the macro expansion.
//
// file: if non-NULL, will be set to the file to which the given
// source location points.
//
// line: if non-NULL, will be set to the line to which the given
// source location points.
//
// column: if non-NULL, will be set to the column to which the given
// source location points.
//
// offset: if non-NULL, will be set to the offset into the
// buffer to which the given source location points.
func (l SourceLocation) ExpansionLocation() (f File, line, column, offset uint) {
	cline := C.uint(0)
	ccol := C.uint(0)
	coff := C.uint(0)
	// FIXME: undefined reference to `clang_getExpansionLocation'
	C.clang_getInstantiationLocation(l.c, &f.c, &cline, &ccol, &coff)
	line = uint(cline)
	column = uint(ccol)
	offset = uint(coff)

	return
}

/**
 * \brief Retrieve the file, line, column, and offset represented by
 * the given source location, as specified in a # line directive.
 *
 * Example: given the following source code in a file somefile.c
 *
 * #123 "dummy.c" 1
 *
 * static int func(void)
 * {
 *     return 0;
 * }
 *
 * the location information returned by this function would be
 *
 * File: dummy.c Line: 124 Column: 12
 *
 * whereas clang_getExpansionLocation would have returned
 *
 * File: somefile.c Line: 3 Column: 12
 *
 * \param location the location within a source file that will be decomposed
 * into its parts.
 *
 * \param filename [out] if non-NULL, will be set to the filename of the
 * source location. Note that filenames returned will be for "virtual" files,
 * which don't necessarily exist on the machine running clang - e.g. when
 * parsing preprocessed output obtained from a different environment. If
 * a non-NULL value is passed in, remember to dispose of the returned value
 * using \c clang_disposeString() once you've finished with it. For an invalid
 * source location, an empty string is returned.
 *
 * \param line [out] if non-NULL, will be set to the line number of the
 * source location. For an invalid source location, zero is returned.
 *
 * \param column [out] if non-NULL, will be set to the column number of the
 * source location. For an invalid source location, zero is returned.
 */
func (l SourceLocation) PresumedLocation() (fname string, line, column uint) {

	cname := cxstring{}
	defer cname.Dispose()
	cline := C.uint(0)
	ccol := C.uint(0)
	C.clang_getPresumedLocation(l.c, &cname.c, &cline, &ccol)
	fname = cname.String()
	line = uint(cline)
	column = uint(ccol)
	return
}

/**
 * \brief Legacy API to retrieve the file, line, column, and offset represented
 * by the given source location.
 *
 * This interface has been replaced by the newer interface
 * \see clang_getExpansionLocation(). See that interface's documentation for
 * details.
 */
func (l SourceLocation) InstantiationLocation() (file File, line, column, offset uint) {

	cline := C.uint(0)
	ccol := C.uint(0)
	coff := C.uint(0)
	C.clang_getInstantiationLocation(l.c,
		&file.c,
		&cline,
		&ccol,
		&coff)
	line = uint(cline)
	column = uint(ccol)
	offset = uint(coff)
	return
}

/**
 * \brief Retrieve the file, line, column, and offset represented by
 * the given source location.
 *
 * If the location refers into a macro instantiation, return where the
 * location was originally spelled in the source file.
 *
 * \param location the location within a source file that will be decomposed
 * into its parts.
 *
 * \param file [out] if non-NULL, will be set to the file to which the given
 * source location points.
 *
 * \param line [out] if non-NULL, will be set to the line to which the given
 * source location points.
 *
 * \param column [out] if non-NULL, will be set to the column to which the given
 * source location points.
 *
 * \param offset [out] if non-NULL, will be set to the offset into the
 * buffer to which the given source location points.
 */
func (l SourceLocation) SpellingLocation() (file File, line, column, offset uint) {

	cline := C.uint(0)
	ccol := C.uint(0)
	coff := C.uint(0)
	C.clang_getSpellingLocation(l.c,
		&file.c,
		&cline,
		&ccol,
		&coff)
	line = uint(cline)
	column = uint(ccol)
	offset = uint(coff)
	return
}
<|endoftext|>


<commit_before>package sync

import (
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/allegro/marathon-consul/apps"
	"github.com/allegro/marathon-consul/marathon"
	"github.com/allegro/marathon-consul/metrics"
	"github.com/allegro/marathon-consul/service"
)

type Sync struct {
	config              Config
	marathon            marathon.Marathoner
	serviceRegistry     service.ServiceRegistry
	syncStartedListener startedListener
}

type startedListener func(apps []*apps.App)

func New(config Config, marathon marathon.Marathoner, serviceRegistry service.ServiceRegistry, syncStartedListener startedListener) *Sync {
	return &Sync{config, marathon, serviceRegistry, syncStartedListener}
}

func (s *Sync) StartSyncServicesJob() {
	if !s.config.Enabled {
		log.Info("Marathon-consul sync disabled")
		return
	}

	log.WithFields(log.Fields{
		"Interval": s.config.Interval,
		"Leader":   s.config.Leader,
		"Force":    s.config.Force,
	}).Info("Marathon-consul sync job started")

	ticker := time.NewTicker(s.config.Interval.Duration)
	go func() {
		if err := s.SyncServices(); err != nil {
			log.WithError(err).Error("An error occured while performing sync")
		}
		for range ticker.C {
			if err := s.SyncServices(); err != nil {
				log.WithError(err).Error("An error occured while performing sync")
			}
		}
	}()
	return
}

func (s *Sync) SyncServices() error {
	var err error
	metrics.Time("sync.services", func() { err = s.syncServices() })
	return err
}

func (s *Sync) syncServices() error {
	if check, err := s.shouldPerformSync(); !check {
		metrics.Clear()
		return err
	}
	log.Info("Syncing services started")

	apps, err := s.marathon.ConsulApps()
	if err != nil {
		return fmt.Errorf("Can't get Marathon apps: %v", err)
	}

	s.syncStartedListener(apps)

	services, err := s.serviceRegistry.GetAllServices()
	if err != nil {
		return fmt.Errorf("Can't get Consul services: %v", err)
	}

	registerCount := s.registerAppTasksNotFoundInConsul(apps, services)
	deregisterCount := s.deregisterConsulServicesNotFoundInMarathon(apps, services)

	metrics.UpdateGauge("sync.register", int64(registerCount))
	metrics.UpdateGauge("sync.deregister", int64(deregisterCount))

	log.Infof("Syncing services finished. Stats, register: %d, deregister: %d", registerCount, deregisterCount)
	return nil
}

func (s *Sync) shouldPerformSync() (bool, error) {
	if s.config.Force {
		log.Debug("Forcing sync")
		return true, nil
	}
	leader, err := s.marathon.Leader()
	if err != nil {
		return false, fmt.Errorf("Could not get Marathon leader: %v", err)
	}
	if s.config.Leader == "" {
		if err = s.resolveHostname(); err != nil {
			return false, fmt.Errorf("Could not resolve hostname: %v", err)
		}
	}
	if leader != s.config.Leader {
		log.WithField("Leader", leader).WithField("Node", s.config.Leader).Debug("Node is not a leader, skipping sync")
		return false, nil
	}
	log.WithField("Node", s.config.Leader).Debug("Node has leadership")
	return true, nil
}

func (s *Sync) resolveHostname() error {
	hostname, err := os.Hostname()
	if err != nil {
		return err
	}
	s.config.Leader = fmt.Sprintf("%s:8080", hostname)
	log.WithField("Leader", s.config.Leader).Info("Marathon-consul sync leader mode set to resolved hostname")
	return nil
}

func (s *Sync) deregisterConsulServicesNotFoundInMarathon(marathonApps []*apps.App, services []*service.Service) int {
	deregisterCount := 0
	runningTasks := s.marathonTaskIdsSet(marathonApps)
	for _, service := range services {
		taskIDInTag, err := service.TaskId()
		taskIDNotFoundInTag := err != nil
		if taskIDNotFoundInTag {
			log.WithField("Id", service.ID).WithError(err).
				Warn("Couldn't extract marathon task id, deregistering since sync should have reregistered it already")
		}

		if _, isRunning := runningTasks[taskIDInTag]; !isRunning || taskIDNotFoundInTag {
			err := s.serviceRegistry.Deregister(service)
			if err != nil {
				log.WithError(err).WithFields(log.Fields{
					"Id":      service.ID,
					"Address": service.RegisteringAgentAddress,
				}).Error("Can't deregister service")
			} else {
				deregisterCount++
			}
		} else {
			log.WithField("Id", service.ID).Debug("Service is running")
		}
	}
	return deregisterCount
}

func (s *Sync) registerAppTasksNotFoundInConsul(marathonApps []*apps.App, services []*service.Service) int {
	registerCount := 0
	registrationsUnderTaskIds := s.taskIdsInConsulServices(services)
	for _, app := range marathonApps {
		if !app.IsConsulApp() {
			log.WithField("Id", app.ID).Debug("Not a Consul app, skipping registration")
			continue
		}
		expectedRegistrations := app.RegistrationIntentsNumber()
		for _, task := range app.Tasks {
			registrations := registrationsUnderTaskIds[task.ID]
			if registrations < expectedRegistrations {
				if registrations != 0 {
					log.WithField("Id", task.ID).WithField("HasRegistrations", registrations).
						WithField("ExpectedRegistrations", expectedRegistrations).Info("Registering missing service registrations")
				}
				if task.IsHealthy() {
					err := s.serviceRegistry.Register(&task, app)
					if err != nil {
						log.WithError(err).WithField("Id", task.ID).Error("Can't register task")
					} else {
						registerCount++
					}
				} else {
					log.WithField("Id", task.ID).Debug("Task is not healthy. Not Registering")
				}
			} else if registrations > expectedRegistrations {
				log.WithField("Id", task.ID).WithField("HasRegistrations", registrations).
					WithField("ExpectedRegistrations", expectedRegistrations).Warn("Skipping task with excess registrations")
			} else {
				log.WithField("Id", task.ID).Debug("Task already registered in Consul")
			}
		}
	}
	return registerCount
}

func (s *Sync) taskIdsInConsulServices(services []*service.Service) map[apps.TaskID]int {
	serviceCounters := make(map[apps.TaskID]int)
	for _, service := range services {
		if taskID, err := service.TaskId(); err == nil {
			serviceCounters[taskID]++
		}
	}
	return serviceCounters
}

func (s *Sync) marathonTaskIdsSet(marathonApps []*apps.App) map[apps.TaskID]struct{} {
	tasksSet := make(map[apps.TaskID]struct{})
	var exists struct{}
	for _, app := range marathonApps {
		for _, task := range app.Tasks {
			tasksSet[task.ID] = exists
		}
	}
	return tasksSet
}
<commit_msg>Log errors during sync (#189)<commit_after>package sync

import (
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/allegro/marathon-consul/apps"
	"github.com/allegro/marathon-consul/marathon"
	"github.com/allegro/marathon-consul/metrics"
	"github.com/allegro/marathon-consul/service"
)

type Sync struct {
	config              Config
	marathon            marathon.Marathoner
	serviceRegistry     service.ServiceRegistry
	syncStartedListener startedListener
}

type startedListener func(apps []*apps.App)

func New(config Config, marathon marathon.Marathoner, serviceRegistry service.ServiceRegistry, syncStartedListener startedListener) *Sync {
	return &Sync{config, marathon, serviceRegistry, syncStartedListener}
}

func (s *Sync) StartSyncServicesJob() {
	if !s.config.Enabled {
		log.Info("Marathon-consul sync disabled")
		return
	}

	log.WithFields(log.Fields{
		"Interval": s.config.Interval,
		"Leader":   s.config.Leader,
		"Force":    s.config.Force,
	}).Info("Marathon-consul sync job started")

	ticker := time.NewTicker(s.config.Interval.Duration)
	go func() {
		if err := s.SyncServices(); err != nil {
			log.WithError(err).Error("An error occured while performing sync")
		}
		for range ticker.C {
			if err := s.SyncServices(); err != nil {
				log.WithError(err).Error("An error occured while performing sync")
			}
		}
	}()
	return
}

func (s *Sync) SyncServices() error {
	var err error
	metrics.Time("sync.services", func() { err = s.syncServices() })
	return err
}

func (s *Sync) syncServices() error {
	if check, err := s.shouldPerformSync(); !check {
		metrics.Clear()
		return err
	}
	log.Info("Syncing services started")

	apps, err := s.marathon.ConsulApps()
	if err != nil {
		return fmt.Errorf("Can't get Marathon apps: %v", err)
	}

	s.syncStartedListener(apps)

	services, err := s.serviceRegistry.GetAllServices()
	if err != nil {
		return fmt.Errorf("Can't get Consul services: %v", err)
	}

	registerCount, registerErrorsCount := s.registerAppTasksNotFoundInConsul(apps, services)
	deregisterCount, deregisterErrorsCount := s.deregisterConsulServicesNotFoundInMarathon(apps, services)

	metrics.UpdateGauge("sync.register.success", int64(registerCount))
	metrics.UpdateGauge("sync.register.error", int64(registerErrorsCount))
	metrics.UpdateGauge("sync.deregister.success", int64(deregisterCount))
	metrics.UpdateGauge("sync.deregister.error", int64(deregisterErrorsCount))

	log.Infof("Syncing services finished. Stats, registerd: %d (failed: %d), deregister: %d (failed: %d).",
		registerCount, registerErrorsCount, deregisterCount, deregisterErrorsCount)
	return nil
}

func (s *Sync) shouldPerformSync() (bool, error) {
	if s.config.Force {
		log.Debug("Forcing sync")
		return true, nil
	}
	leader, err := s.marathon.Leader()
	if err != nil {
		return false, fmt.Errorf("Could not get Marathon leader: %v", err)
	}
	if s.config.Leader == "" {
		if err = s.resolveHostname(); err != nil {
			return false, fmt.Errorf("Could not resolve hostname: %v", err)
		}
	}
	if leader != s.config.Leader {
		log.WithField("Leader", leader).WithField("Node", s.config.Leader).Debug("Node is not a leader, skipping sync")
		return false, nil
	}
	log.WithField("Node", s.config.Leader).Debug("Node has leadership")
	return true, nil
}

func (s *Sync) resolveHostname() error {
	hostname, err := os.Hostname()
	if err != nil {
		return err
	}
	s.config.Leader = fmt.Sprintf("%s:8080", hostname)
	log.WithField("Leader", s.config.Leader).Info("Marathon-consul sync leader mode set to resolved hostname")
	return nil
}

func (s *Sync) deregisterConsulServicesNotFoundInMarathon(marathonApps []*apps.App, services []*service.Service) (deregisterCount int, errorCount int) {
	runningTasks := marathonTaskIdsSet(marathonApps)
	for _, service := range services {
		taskIDInTag, err := service.TaskId()
		taskIDNotFoundInTag := err != nil
		if taskIDNotFoundInTag {
			log.WithField("Id", service.ID).WithError(err).
				Warn("Couldn't extract marathon task id, deregistering since sync should have reregistered it already")
		}

		if _, isRunning := runningTasks[taskIDInTag]; !isRunning || taskIDNotFoundInTag {
			err := s.serviceRegistry.Deregister(service)
			if err != nil {
				log.WithError(err).WithFields(log.Fields{
					"Id":      service.ID,
					"Address": service.RegisteringAgentAddress,
					"Sync": true,
				}).Error("Can't deregister service")
				errorCount++
			} else {
				deregisterCount++
			}
		} else {
			log.WithField("Id", service.ID).Debug("Service is running")
		}
	}
	return
}

func (s *Sync) registerAppTasksNotFoundInConsul(marathonApps []*apps.App, services []*service.Service) (registerCount int, errorCount int) {
	registrationsUnderTaskIds := taskIdsInConsulServices(services)
	for _, app := range marathonApps {
		if !app.IsConsulApp() {
			log.WithField("Id", app.ID).Debug("Not a Consul app, skipping registration")
			continue
		}
		expectedRegistrations := app.RegistrationIntentsNumber()
		for _, task := range app.Tasks {
			registrations := registrationsUnderTaskIds[task.ID]
			logFields := log.Fields{
				"Id": task.ID,
				"HasRegistrations": registrations,
				"ExpectedRegistrations": expectedRegistrations,
				"Sync": true,
			}
			if registrations < expectedRegistrations {
				if registrations != 0 {
					log.WithFields(logFields).Info("Registering missing service registrations")
				}
				if task.IsHealthy() {
					err := s.serviceRegistry.Register(&task, app)
					if err != nil {
						log.WithError(err).WithFields(logFields).Error("Can't register task")
						errorCount++
					} else {
						registerCount++
					}
				} else {
					log.WithFields(logFields).Debug("Task is not healthy. Not Registering")
				}
			} else if registrations > expectedRegistrations {
				log.WithFields(logFields).Warn("Skipping task with excess registrations")
			} else {
				log.WithFields(logFields).Debug("Task already registered in Consul")
			}
		}
	}
	return
}

func taskIdsInConsulServices(services []*service.Service) map[apps.TaskID]int {
	serviceCounters := make(map[apps.TaskID]int)
	for _, service := range services {
		if taskID, err := service.TaskId(); err == nil {
			serviceCounters[taskID]++
		}
	}
	return serviceCounters
}

func marathonTaskIdsSet(marathonApps []*apps.App) map[apps.TaskID]struct{} {
	tasksSet := make(map[apps.TaskID]struct{})
	var exists struct{}
	for _, app := range marathonApps {
		for _, task := range app.Tasks {
			tasksSet[task.ID] = exists
		}
	}
	return tasksSet
}
<|endoftext|>


<commit_before>// Copyright 2016 Maarten Everts. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gabi

// BaseParameters holds the base system parameters
type BaseParameters struct {
	Le      uint
	LePrime uint
	Lh      uint
	Lm      uint
	Ln      uint
	Lstatzk uint
	Lv      uint
}

// defaultBaseParameters holds per keylength the base parameters.
var defaultBaseParameters = map[int]BaseParameters{
	1024: BaseParameters{
		Le:      597,
		LePrime: 120,
		Lh:      256,
		Lm:      256,
		Ln:      1024,
		Lstatzk: 80,
		Lv:      1700,
	},
	2048: BaseParameters{
		Le:      597,
		LePrime: 120,
		Lh:      256,
		Lm:      256,
		Ln:      2048,
		Lstatzk: 128,
		Lv:      1700,
	},
	4096: BaseParameters{
		Le:      597,
		LePrime: 120,
		Lh:      256,
		Lm:      512,
		Ln:      4096,
		Lstatzk: 128,
		Lv:      1700,
	},
}

// DerivedParameters holds system parameters that can be drived from base
// systemparameters (BaseParameters)
type DerivedParameters struct {
	LeCommit      uint
	LmCommit      uint
	LRA           uint
	LsCommit      uint
	LvCommit      uint
	LvPrime       uint
	LvPrimeCommit uint
}

// MakeDerivedParameters computes the derived system parameters
func MakeDerivedParameters(base BaseParameters) DerivedParameters {
	return DerivedParameters{
		LeCommit:      base.LePrime + base.Lstatzk + base.Lh,
		LmCommit:      base.Lm + base.Lstatzk + base.Lh,
		LRA:           base.Ln + base.Lstatzk,
		LsCommit:      base.Lm + base.Lstatzk + base.Lh + 1,
		LvCommit:      base.Lv + base.Lstatzk + base.Lh,
		LvPrime:       base.Ln + base.Lstatzk,
		LvPrimeCommit: base.Ln + 2*base.Lstatzk + base.Lh,
	}
}

// SystemParameters holds the system parameters of the IRMA system.
type SystemParameters struct {
	BaseParameters
	DerivedParameters
}

// DefaultSystemParameters holds per keylength the default parameters as are
// currently in use at the moment. This might (and probably will) change in the
// future.
var DefaultSystemParameters = map[int]*SystemParameters{
	1024: &SystemParameters{defaultBaseParameters[1024], MakeDerivedParameters(defaultBaseParameters[1024])},
	2048: &SystemParameters{defaultBaseParameters[2048], MakeDerivedParameters(defaultBaseParameters[2048])},
	4096: &SystemParameters{defaultBaseParameters[4096], MakeDerivedParameters(defaultBaseParameters[4096])},
}

// ParamSize computes the size of a parameter in bytes given the size in bits.
func ParamSize(a int) int {
	return (a + 8 - 1) / 8
}
<commit_msg>Add list of available key sizes.<commit_after>// Copyright 2016 Maarten Everts. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gabi

import (
	"sort"
)

// BaseParameters holds the base system parameters
type BaseParameters struct {
	Le      uint
	LePrime uint
	Lh      uint
	Lm      uint
	Ln      uint
	Lstatzk uint
	Lv      uint
}

// defaultBaseParameters holds per keylength the base parameters.
var defaultBaseParameters = map[int]BaseParameters{
	1024: BaseParameters{
		Le:      597,
		LePrime: 120,
		Lh:      256,
		Lm:      256,
		Ln:      1024,
		Lstatzk: 80,
		Lv:      1700,
	},
	2048: BaseParameters{
		Le:      597,
		LePrime: 120,
		Lh:      256,
		Lm:      256,
		Ln:      2048,
		Lstatzk: 128,
		Lv:      1700,
	},
	4096: BaseParameters{
		Le:      597,
		LePrime: 120,
		Lh:      256,
		Lm:      512,
		Ln:      4096,
		Lstatzk: 128,
		Lv:      1700,
	},
}

// DerivedParameters holds system parameters that can be drived from base
// systemparameters (BaseParameters)
type DerivedParameters struct {
	LeCommit      uint
	LmCommit      uint
	LRA           uint
	LsCommit      uint
	LvCommit      uint
	LvPrime       uint
	LvPrimeCommit uint
}

// MakeDerivedParameters computes the derived system parameters
func MakeDerivedParameters(base BaseParameters) DerivedParameters {
	return DerivedParameters{
		LeCommit:      base.LePrime + base.Lstatzk + base.Lh,
		LmCommit:      base.Lm + base.Lstatzk + base.Lh,
		LRA:           base.Ln + base.Lstatzk,
		LsCommit:      base.Lm + base.Lstatzk + base.Lh + 1,
		LvCommit:      base.Lv + base.Lstatzk + base.Lh,
		LvPrime:       base.Ln + base.Lstatzk,
		LvPrimeCommit: base.Ln + 2*base.Lstatzk + base.Lh,
	}
}

// SystemParameters holds the system parameters of the IRMA system.
type SystemParameters struct {
	BaseParameters
	DerivedParameters
}

// DefaultSystemParameters holds per keylength the default parameters as are
// currently in use at the moment. This might (and probably will) change in the
// future.
var DefaultSystemParameters = map[int]*SystemParameters{
	1024: &SystemParameters{defaultBaseParameters[1024], MakeDerivedParameters(defaultBaseParameters[1024])},
	2048: &SystemParameters{defaultBaseParameters[2048], MakeDerivedParameters(defaultBaseParameters[2048])},
	4096: &SystemParameters{defaultBaseParameters[4096], MakeDerivedParameters(defaultBaseParameters[4096])},
}

// getAvailableKeyLengths returns the keylengths for the provided map of system
// parameters.
func getAvailableKeyLengths(sysParamsMap map[int]*SystemParameters) []int {
	lengths := make([]int, 0, len(sysParamsMap))
	for k := range sysParamsMap {
		lengths = append(lengths, k)
	}
	sort.Ints(lengths)
	return lengths
}

// DefaultKeyLengths is a slice of integers holding the keylengths for which
// system parameters are available.
var DefaultKeyLengths = getAvailableKeyLengths(DefaultSystemParameters)

// ParamSize computes the size of a parameter in bytes given the size in bits.
func ParamSize(a int) int {
	return (a + 8 - 1) / 8
}
<|endoftext|>


<commit_before>package tail

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

const (
	RotateMarker   = "__ROTATE__\n"
	TruncateMarker = "__TRUNCATE__\n"
	EOFMarker      = "__EOF__\n"
)

var Logs = []string{
	"single line\n",
	"multi line 1\nmulti line 2\nmulti line 3\n",
	"continuous line 1", "continuous line 2", "continuous line 3\n",
	RotateMarker,
	"foo\n",
	"bar\n",
	"baz\n",
	TruncateMarker,
	"FOOOO\n",
	"BAAAR\n",
	"BAZZZZZZZ\n",
	EOFMarker,
}

func TestTail(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go writeFile(tmpdir, t)
	tail, err := New(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected := strings.Join(Logs, "")
	actual, err := recieve(tail, t)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}
}

func writeFile(tmpdir string, t *testing.T) error {
	time.Sleep(1 * time.Second) // wait for start Tail...

	filename := filepath.Join(tmpdir, "test.log")
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	for _, line := range Logs {
		_, err := file.WriteString(line)
		if err != nil {
			return err
		}
		t.Logf("write: %s", line)
		switch line {
		case RotateMarker:
			file.Close()
			os.Rename(filename, filename+".old")
			file, _ = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
		case TruncateMarker:
			time.Sleep(1 * time.Second)
			os.Truncate(filename, 0)
			file.Seek(int64(0), os.SEEK_SET)
		}
		time.Sleep(1 * time.Millisecond)
	}

	file.Close()
	return nil
}

func recieve(tail *Tail, t *testing.T) (string, error) {
	actual := ""
	for {
		select {
		case line := <-tail.Lines:
			t.Logf("recieved: %s", line.Text)
			actual += line.Text
			if line.Text == EOFMarker {
				return actual, nil
			}
		case err := <-tail.Errors:
			return "", err
		case <-time.After(5 * time.Second):
			return "", errors.New("timeout")
		}
	}
	return actual, nil
}
<commit_msg>long wait for start tail<commit_after>package tail

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

const (
	RotateMarker   = "__ROTATE__\n"
	TruncateMarker = "__TRUNCATE__\n"
	EOFMarker      = "__EOF__\n"
)

var Logs = []string{
	"single line\n",
	"multi line 1\nmulti line 2\nmulti line 3\n",
	"continuous line 1", "continuous line 2", "continuous line 3\n",
	RotateMarker,
	"foo\n",
	"bar\n",
	"baz\n",
	TruncateMarker,
	"FOOOO\n",
	"BAAAR\n",
	"BAZZZZZZZ\n",
	EOFMarker,
}

func TestTail(t *testing.T) {
	tmpdir, err := ioutil.TempDir("", "go-tail.")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer os.RemoveAll(tmpdir)

	go writeFile(tmpdir, t)
	tail, err := New(filepath.Join(tmpdir, "test.log"))
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	expected := strings.Join(Logs, "")
	actual, err := recieve(tail, t)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if actual != expected {
		t.Errorf("got %s\nwant %s", actual, expected)
	}
}

func writeFile(tmpdir string, t *testing.T) error {
	time.Sleep(2 * time.Second) // wait for start Tail...

	filename := filepath.Join(tmpdir, "test.log")
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}

	for _, line := range Logs {
		_, err := file.WriteString(line)
		if err != nil {
			return err
		}
		t.Logf("write: %s", line)
		switch line {
		case RotateMarker:
			file.Close()
			os.Rename(filename, filename+".old")
			file, _ = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
		case TruncateMarker:
			time.Sleep(1 * time.Second)
			os.Truncate(filename, 0)
			file.Seek(int64(0), os.SEEK_SET)
		}
		time.Sleep(1 * time.Millisecond)
	}

	file.Close()
	return nil
}

func recieve(tail *Tail, t *testing.T) (string, error) {
	actual := ""
	for {
		select {
		case line := <-tail.Lines:
			t.Logf("recieved: %s", line.Text)
			actual += line.Text
			if line.Text == EOFMarker {
				return actual, nil
			}
		case err := <-tail.Errors:
			return "", err
		case <-time.After(5 * time.Second):
			return "", errors.New("timeout")
		}
	}
	return actual, nil
}
<|endoftext|>


<commit_before>package task

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
)

var (
	errUnavailable = errors.New("runner is unavailable")
)

var (
	defaultWaitStoppedTimeout       = time.Minute
	defaultQueueName                = "__default__"
	batch                     int64 = 64

	jobPool sync.Pool
)

type state int

const (
	running  = state(0)
	stopping = state(1)
	stopped  = state(2)
)

var (
	// ErrJobCancelled error job cancelled
	ErrJobCancelled = errors.New("Job cancelled")
)

// JobState is the job state
type JobState int

const (
	// Pending job is wait to running
	Pending = JobState(0)
	// Running job is running
	Running = JobState(1)
	// Cancelling job is cancelling
	Cancelling = JobState(2)
	// Cancelled job is cancelled
	Cancelled = JobState(3)
	// Finished job is complete
	Finished = JobState(4)
	// Failed job is failed when execute
	Failed = JobState(5)
)

func acquireJob() *Job {
	v := jobPool.Get()
	if v == nil {
		return &Job{}
	}
	return v.(*Job)
}

func releaseJob(job *Job) {
	job.reset()
	jobPool.Put(job)
}

// Job is do for something with state
type Job struct {
	sync.RWMutex

	desc        string
	fun         func() error
	state       JobState
	result      interface{}
	needRelease bool
}

func newJob(desc string, fun func() error) *Job {
	job := acquireJob()
	job.fun = fun
	job.state = Pending
	job.desc = desc
	job.needRelease = true

	return job
}

func (job *Job) reset() {
	job.Lock()
	job.fun = nil
	job.state = Pending
	job.desc = ""
	job.result = nil
	job.needRelease = true
	job.Unlock()
}

// IsComplete return true means the job is complete.
func (job *Job) IsComplete() bool {
	return !job.IsNotComplete()
}

// IsNotComplete return true means the job is not complete.
func (job *Job) IsNotComplete() bool {
	job.RLock()
	yes := job.state == Pending || job.state == Running || job.state == Cancelling
	job.RUnlock()

	return yes
}

// SetResult set result
func (job *Job) SetResult(result interface{}) {
	job.Lock()
	job.result = result
	job.Unlock()
}

// GetResult returns job result
func (job *Job) GetResult() interface{} {
	job.RLock()
	r := job.result
	job.RUnlock()
	return r
}

// Cancel cancel the job
func (job *Job) Cancel() {
	job.Lock()
	if job.state == Pending {
		job.state = Cancelling
	}
	job.Unlock()
}

// IsRunning returns true if job state is Running
func (job *Job) IsRunning() bool {
	return job.isSpecState(Running)
}

// IsPending returns true if job state is Pending
func (job *Job) IsPending() bool {
	return job.isSpecState(Pending)
}

// IsFinished returns true if job state is Finished
func (job *Job) IsFinished() bool {
	return job.isSpecState(Finished)
}

// IsCancelling returns true if job state is Cancelling
func (job *Job) IsCancelling() bool {
	return job.isSpecState(Cancelling)
}

// IsCancelled returns true if job state is Cancelled
func (job *Job) IsCancelled() bool {
	return job.isSpecState(Cancelled)
}

// IsFailed returns true if job state is Failed
func (job *Job) IsFailed() bool {
	return job.isSpecState(Failed)
}

func (job *Job) isSpecState(spec JobState) bool {
	job.RLock()
	yes := job.state == spec
	job.RUnlock()

	return yes
}

func (job *Job) setState(state JobState) {
	job.state = state
}

// Runner TODO
type Runner struct {
	sync.RWMutex

	stop       sync.WaitGroup
	stopC      chan struct{}
	lastID     uint64
	cancels    map[uint64]context.CancelFunc
	state      state
	namedQueue map[string]*Queue
	tasks      map[string]bool
}

// NewRunner returns a task runner
func NewRunner() *Runner {
	t := &Runner{
		stopC:      make(chan struct{}),
		state:      running,
		namedQueue: make(map[string]*Queue),
		cancels:    make(map[uint64]context.CancelFunc),
		tasks:      make(map[string]bool),
	}

	t.AddNamedWorker(defaultQueueName, func() {})
	return t
}

// AddNamedWorker add a named worker, the named worker has uniq queue, so jobs are linear execution
func (s *Runner) AddNamedWorker(name string, stopped func()) (uint64, error) {
	s.Lock()
	defer s.Unlock()

	if s.state != running {
		return 0, errUnavailable
	}

	if _, ok := s.namedQueue[name]; ok {
		return 0, fmt.Errorf("%s worker already added", name)
	}

	id, ctx := s.allocCtxLocked()
	q := NewWithContext(128, ctx)
	s.namedQueue[name] = q
	s.startWorkerLocked(ctx, name, q, stopped)
	return id, nil
}

// IsNamedWorkerBusy returns true if named queue is not empty
func (s *Runner) IsNamedWorkerBusy(worker string) bool {
	s.RLock()
	defer s.RUnlock()

	return s.getNamedQueueLocked(worker).Len() > 0
}

func (s *Runner) startWorkerLocked(ctx context.Context, name string, q *Queue, stopped func()) {
	s.doRunCancelableTaskLocked(ctx, name, func(ctx context.Context) {
		jobs := make([]interface{}, batch)
		if stopped != nil {
			defer stopped()
		}

		for {
			n, err := q.Get(batch, jobs)
			if err != nil {
				return
			}

			for i := int64(0); i < n; i++ {
				job := jobs[i].(*Job)

				job.Lock()

				switch job.state {
				case Pending:
					job.setState(Running)
					job.Unlock()
					err := job.fun()
					job.Lock()
					if err != nil {
						if err == ErrJobCancelled {
							job.setState(Cancelled)
						} else {
							job.setState(Failed)
						}
					} else {
						if job.state == Cancelling {
							job.setState(Cancelled)
						} else {
							job.setState(Finished)
						}
					}
				case Cancelling:
					job.setState(Cancelled)
				}

				job.Unlock()

				if job.needRelease {
					releaseJob(job)
				}
			}
		}
	})
}

// RunJob run a job
func (s *Runner) RunJob(desc string, task func() error) error {
	return s.RunJobWithNamedWorker(desc, defaultQueueName, task)
}

// RunJobWithNamedWorker run a job in a named worker
func (s *Runner) RunJobWithNamedWorker(desc, worker string, task func() error) error {
	return s.RunJobWithNamedWorkerWithCB(desc, worker, task, nil)
}

// RunJobWithNamedWorkerWithCB run a job in a named worker
func (s *Runner) RunJobWithNamedWorkerWithCB(desc, worker string, task func() error, cb func(*Job)) error {
	s.Lock()

	if s.state != running {
		s.Unlock()
		return errUnavailable
	}

	job := newJob(desc, task)
	q := s.getNamedQueueLocked(worker)
	if q == nil {
		s.Unlock()
		return fmt.Errorf("named worker %s is not exists", worker)
	}

	if cb != nil {
		job.needRelease = false
		cb(job)
	}

	q.Put(job)

	s.Unlock()
	return nil
}

// RunCancelableTask run a task that can be cancelled
// Example:
// err := s.RunCancelableTask(func(ctx context.Context) {
// 	select {
// 	case <-ctx.Done():
// 	// cancelled
// 	case <-time.After(time.Second):
// 		// do something
// 	}
// })
// if err != nil {
// 	// hanle error
// 	return
// }
func (s *Runner) RunCancelableTask(name string, task func(context.Context)) (uint64, error) {
	s.Lock()
	defer s.Unlock()

	if s.state != running {
		return 0, errUnavailable
	}

	id, ctx := s.allocCtxLocked()
	s.doRunCancelableTaskLocked(ctx, name, task)
	return id, nil
}

// RunTask runs a task in new goroutine
func (s *Runner) RunTask(task func()) error {
	s.Lock()
	defer s.Unlock()

	if s.state != running {
		return errUnavailable
	}

	s.stop.Add(1)

	go func() {
		defer s.stop.Done()
		task()
	}()

	return nil
}

// StopCancelableTask stop cancelable spec task
func (s *Runner) StopCancelableTask(id uint64) error {
	s.Lock()
	defer s.Unlock()

	if s.state == stopping ||
		s.state == stopped {
		return errors.New("stopper is already stoppped")
	}

	cancel, ok := s.cancels[id]
	if !ok {
		return fmt.Errorf("target task<%d> not found", id)
	}

	delete(s.cancels, id)
	cancel()
	return nil
}

// Stop stop all task
// RunTask will failure with an error
// Wait complete for the tasks that already in execute
// Cancel the tasks that is not start
func (s *Runner) StopWithTimeout(timeout time.Duration) ([]string, error) {
	return s.doStop(timeout)
}

// Stop stop all task
// RunTask will failure with an error
// Wait complete for the tasks that already in execute
// Cancel the tasks that is not start
func (s *Runner) Stop() ([]string, error) {
	return s.doStop(defaultWaitStoppedTimeout)
}

func (s *Runner) doStop(timeout time.Duration) ([]string, error) {
	s.Lock()
	if s.state == stopping ||
		s.state == stopped {
		s.Unlock()
		return nil, errors.New("stopper is already stoppped")
	}
	s.state = stopping

	for _, cancel := range s.cancels {
		cancel()
	}
	s.Unlock()

	go func() {
		s.stop.Wait()
		s.stopC <- struct{}{}
	}()

	select {
	case <-time.After(timeout):
		s.Lock()
		defer s.Unlock()
		var timeoutWorkers []string
		for name, run := range s.tasks {
			if run {
				timeoutWorkers = append(timeoutWorkers, name)
			}
		}
		return timeoutWorkers, errors.New("waitting for task complete timeout")
	case <-s.stopC:
	}

	s.Lock()
	defer s.Unlock()
	s.state = stopped
	return nil, nil
}

func (s *Runner) doRunCancelableTaskLocked(ctx context.Context, name string, task func(context.Context)) {
	s.tasks[name] = true
	go func() {
		if err := recover(); err != nil {
			panic(err)
		}
		defer func() {
			s.stop.Done()
			s.tasks[name] = false
		}()
		task(ctx)
	}()
}

func (s *Runner) getNamedQueueLocked(name string) *Queue {
	return s.namedQueue[name]
}

func (s *Runner) allocCtxLocked() (uint64, context.Context) {
	ctx, cancel := context.WithCancel(context.Background())
	s.lastID++
	id := s.lastID
	s.cancels[id] = cancel
	s.stop.Add(1)
	return id, ctx
}
<commit_msg>Fix crash<commit_after>package task

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"time"
)

var (
	errUnavailable = errors.New("runner is unavailable")
)

var (
	defaultWaitStoppedTimeout       = time.Minute
	defaultQueueName                = "__default__"
	batch                     int64 = 64

	jobPool sync.Pool
)

type state int

const (
	running  = state(0)
	stopping = state(1)
	stopped  = state(2)
)

var (
	// ErrJobCancelled error job cancelled
	ErrJobCancelled = errors.New("Job cancelled")
)

// JobState is the job state
type JobState int

const (
	// Pending job is wait to running
	Pending = JobState(0)
	// Running job is running
	Running = JobState(1)
	// Cancelling job is cancelling
	Cancelling = JobState(2)
	// Cancelled job is cancelled
	Cancelled = JobState(3)
	// Finished job is complete
	Finished = JobState(4)
	// Failed job is failed when execute
	Failed = JobState(5)
)

func acquireJob() *Job {
	v := jobPool.Get()
	if v == nil {
		return &Job{}
	}
	return v.(*Job)
}

func releaseJob(job *Job) {
	job.reset()
	jobPool.Put(job)
}

// Job is do for something with state
type Job struct {
	sync.RWMutex

	desc        string
	fun         func() error
	state       JobState
	result      interface{}
	needRelease bool
}

func newJob(desc string, fun func() error) *Job {
	job := acquireJob()
	job.fun = fun
	job.state = Pending
	job.desc = desc
	job.needRelease = true

	return job
}

func (job *Job) reset() {
	job.Lock()
	job.fun = nil
	job.state = Pending
	job.desc = ""
	job.result = nil
	job.needRelease = true
	job.Unlock()
}

// IsComplete return true means the job is complete.
func (job *Job) IsComplete() bool {
	return !job.IsNotComplete()
}

// IsNotComplete return true means the job is not complete.
func (job *Job) IsNotComplete() bool {
	job.RLock()
	yes := job.state == Pending || job.state == Running || job.state == Cancelling
	job.RUnlock()

	return yes
}

// SetResult set result
func (job *Job) SetResult(result interface{}) {
	job.Lock()
	job.result = result
	job.Unlock()
}

// GetResult returns job result
func (job *Job) GetResult() interface{} {
	job.RLock()
	r := job.result
	job.RUnlock()
	return r
}

// Cancel cancel the job
func (job *Job) Cancel() {
	job.Lock()
	if job.state == Pending {
		job.state = Cancelling
	}
	job.Unlock()
}

// IsRunning returns true if job state is Running
func (job *Job) IsRunning() bool {
	return job.isSpecState(Running)
}

// IsPending returns true if job state is Pending
func (job *Job) IsPending() bool {
	return job.isSpecState(Pending)
}

// IsFinished returns true if job state is Finished
func (job *Job) IsFinished() bool {
	return job.isSpecState(Finished)
}

// IsCancelling returns true if job state is Cancelling
func (job *Job) IsCancelling() bool {
	return job.isSpecState(Cancelling)
}

// IsCancelled returns true if job state is Cancelled
func (job *Job) IsCancelled() bool {
	return job.isSpecState(Cancelled)
}

// IsFailed returns true if job state is Failed
func (job *Job) IsFailed() bool {
	return job.isSpecState(Failed)
}

func (job *Job) isSpecState(spec JobState) bool {
	job.RLock()
	yes := job.state == spec
	job.RUnlock()

	return yes
}

func (job *Job) setState(state JobState) {
	job.state = state
}

// Runner TODO
type Runner struct {
	sync.RWMutex

	stop       sync.WaitGroup
	stopC      chan struct{}
	lastID     uint64
	cancels    map[uint64]context.CancelFunc
	state      state
	namedQueue map[string]*Queue
	tasks      map[string]bool
}

// NewRunner returns a task runner
func NewRunner() *Runner {
	t := &Runner{
		stopC:      make(chan struct{}),
		state:      running,
		namedQueue: make(map[string]*Queue),
		cancels:    make(map[uint64]context.CancelFunc),
		tasks:      make(map[string]bool),
	}

	t.AddNamedWorker(defaultQueueName, func() {})
	return t
}

// AddNamedWorker add a named worker, the named worker has uniq queue, so jobs are linear execution
func (s *Runner) AddNamedWorker(name string, stopped func()) (uint64, error) {
	s.Lock()
	defer s.Unlock()

	if s.state != running {
		return 0, errUnavailable
	}

	if _, ok := s.namedQueue[name]; ok {
		return 0, fmt.Errorf("%s worker already added", name)
	}

	id, ctx := s.allocCtxLocked()
	q := NewWithContext(128, ctx)
	s.namedQueue[name] = q
	s.startWorkerLocked(ctx, name, q, stopped)
	return id, nil
}

// IsNamedWorkerBusy returns true if named queue is not empty
func (s *Runner) IsNamedWorkerBusy(worker string) bool {
	s.RLock()
	defer s.RUnlock()

	return s.getNamedQueueLocked(worker).Len() > 0
}

func (s *Runner) startWorkerLocked(ctx context.Context, name string, q *Queue, stopped func()) {
	s.doRunCancelableTaskLocked(ctx, name, func(ctx context.Context) {
		jobs := make([]interface{}, batch)
		if stopped != nil {
			defer stopped()
		}

		for {
			n, err := q.Get(batch, jobs)
			if err != nil {
				return
			}

			for i := int64(0); i < n; i++ {
				job := jobs[i].(*Job)

				job.Lock()

				switch job.state {
				case Pending:
					job.setState(Running)
					job.Unlock()
					err := job.fun()
					job.Lock()
					if err != nil {
						if err == ErrJobCancelled {
							job.setState(Cancelled)
						} else {
							job.setState(Failed)
						}
					} else {
						if job.state == Cancelling {
							job.setState(Cancelled)
						} else {
							job.setState(Finished)
						}
					}
				case Cancelling:
					job.setState(Cancelled)
				}

				job.Unlock()

				if job.needRelease {
					releaseJob(job)
				}
			}
		}
	})
}

// RunJob run a job
func (s *Runner) RunJob(desc string, task func() error) error {
	return s.RunJobWithNamedWorker(desc, defaultQueueName, task)
}

// RunJobWithNamedWorker run a job in a named worker
func (s *Runner) RunJobWithNamedWorker(desc, worker string, task func() error) error {
	return s.RunJobWithNamedWorkerWithCB(desc, worker, task, nil)
}

// RunJobWithNamedWorkerWithCB run a job in a named worker
func (s *Runner) RunJobWithNamedWorkerWithCB(desc, worker string, task func() error, cb func(*Job)) error {
	s.Lock()

	if s.state != running {
		s.Unlock()
		return errUnavailable
	}

	job := newJob(desc, task)
	q := s.getNamedQueueLocked(worker)
	if q == nil {
		s.Unlock()
		return fmt.Errorf("named worker %s is not exists", worker)
	}

	if cb != nil {
		job.needRelease = false
		cb(job)
	}

	q.Put(job)

	s.Unlock()
	return nil
}

// RunCancelableTask run a task that can be cancelled
// Example:
// err := s.RunCancelableTask(func(ctx context.Context) {
// 	select {
// 	case <-ctx.Done():
// 	// cancelled
// 	case <-time.After(time.Second):
// 		// do something
// 	}
// })
// if err != nil {
// 	// hanle error
// 	return
// }
func (s *Runner) RunCancelableTask(name string, task func(context.Context)) (uint64, error) {
	s.Lock()
	defer s.Unlock()

	if s.state != running {
		return 0, errUnavailable
	}

	id, ctx := s.allocCtxLocked()
	s.doRunCancelableTaskLocked(ctx, name, task)
	return id, nil
}

// RunTask runs a task in new goroutine
func (s *Runner) RunTask(task func()) error {
	s.Lock()
	defer s.Unlock()

	if s.state != running {
		return errUnavailable
	}

	s.stop.Add(1)

	go func() {
		defer s.stop.Done()
		task()
	}()

	return nil
}

// StopCancelableTask stop cancelable spec task
func (s *Runner) StopCancelableTask(id uint64) error {
	s.Lock()
	defer s.Unlock()

	if s.state == stopping ||
		s.state == stopped {
		return errors.New("stopper is already stoppped")
	}

	cancel, ok := s.cancels[id]
	if !ok {
		return fmt.Errorf("target task<%d> not found", id)
	}

	delete(s.cancels, id)
	cancel()
	return nil
}

// Stop stop all task
// RunTask will failure with an error
// Wait complete for the tasks that already in execute
// Cancel the tasks that is not start
func (s *Runner) StopWithTimeout(timeout time.Duration) ([]string, error) {
	return s.doStop(timeout)
}

// Stop stop all task
// RunTask will failure with an error
// Wait complete for the tasks that already in execute
// Cancel the tasks that is not start
func (s *Runner) Stop() ([]string, error) {
	return s.doStop(defaultWaitStoppedTimeout)
}

func (s *Runner) doStop(timeout time.Duration) ([]string, error) {
	s.Lock()
	if s.state == stopping ||
		s.state == stopped {
		s.Unlock()
		return nil, errors.New("stopper is already stoppped")
	}
	s.state = stopping

	for _, cancel := range s.cancels {
		cancel()
	}
	s.Unlock()

	go func() {
		s.stop.Wait()
		s.stopC <- struct{}{}
	}()

	select {
	case <-time.After(timeout):
		s.Lock()
		defer s.Unlock()
		var timeoutWorkers []string
		for name, run := range s.tasks {
			if run {
				timeoutWorkers = append(timeoutWorkers, name)
			}
		}
		return timeoutWorkers, errors.New("waitting for task complete timeout")
	case <-s.stopC:
	}

	s.Lock()
	defer s.Unlock()
	s.state = stopped
	return nil, nil
}

func (s *Runner) doRunCancelableTaskLocked(ctx context.Context, name string, task func(context.Context)) {
	s.tasks[name] = true
	go func() {
		if err := recover(); err != nil {
			panic(err)
		}
		defer func() {
			s.stop.Done()
			s.Lock()
			s.tasks[name] = false
			s.Unlock()
		}()
		task(ctx)
	}()
}

func (s *Runner) getNamedQueueLocked(name string) *Queue {
	return s.namedQueue[name]
}

func (s *Runner) allocCtxLocked() (uint64, context.Context) {
	ctx, cancel := context.WithCancel(context.Background())
	s.lastID++
	id := s.lastID
	s.cancels[id] = cancel
	s.stop.Add(1)
	return id, ctx
}
<|endoftext|>


<commit_before>package main

import (
	"html/template"
	"time"
)

var (
	tmpl    *template.Template
	funcMap = template.FuncMap{
		"dateFmt": dateFmt,
	}
)

func init() {
	tmpl = template.Must(template.New("home").Funcs(funcMap).Parse(page))
	tmpl = tmpl.Funcs(funcMap)
}

func dateFmt(when interface{}) string {
	t := when.(time.Time)
	if t.IsZero() {
		return ""
	}
	return t.Format(layout)
}

const (
	page = `<!DOCTYPE html>

<title>Netstats</title>

Netstats

Started: {{.Started}}

Uptime: {{.Uptime}}

{{ range $key,$stat := .SnmpStats }}

{{$key}}

Get count: {{$stat.GetCnt}}

Error count: {{$stat.ErrCnt}}

{{ if $stat.LastError }}

Last error: {{$stat.LastError}} ({{dateFmt $stat.LastTime}})

{{ end }}

{{ end }}

Config

{{ range $key,$snmp := .SNMP }}

SNMP {{$key}}

Host: {{$snmp.Host}}

Freq: {{$snmp.Freq}}

Retries: {{$snmp.Retries}}

Timeout: {{$snmp.Timeout}}

{{ end}}
{{ range $key,$influx := .Influx }}

Influx {{$key}}

Host: {{$influx.Host}}

Database: {{$influx.Database}}

{{/*

Sent: {{$influx.Sent}}

Errors: {{$influx.Errors}}

*/}}

{{ end }}

Profiler

`
)
<commit_msg>Update templates.go<commit_after>package main

import (
	"html/template"
	"time"
)

var (
	tmpl    *template.Template
	funcMap = template.FuncMap{
		"dateFmt": dateFmt,
	}
)

func init() {
	tmpl = template.Must(template.New("home").Funcs(funcMap).Parse(page))
	tmpl = tmpl.Funcs(funcMap)
}

func dateFmt(when interface{}) string {
	t := when.(time.Time)
	if t.IsZero() {
		return ""
	}
	return t.Format(layout)
}

const (
	page = `<!DOCTYPE html>

<title>Netstats</title>

Netstats

Started: {{.Started}}

Uptime: {{.Uptime}}

{{ range $key,$stat := .SnmpStats }}

{{$key}}

Get count: {{$stat.GetCnt}}

Error count: {{$stat.ErrCnt}}

{{ if $stat.LastError }}

Last error: {{$stat.LastError}} ({{dateFmt $stat.LastTime}})

{{ end }}

{{ end }}

Config

{{ range $key,$snmp := .SNMP }}

SNMP {{$key}}

Host: {{$snmp.Host}}

Freq: {{$snmp.Freq}}

Retries: {{$snmp.Retries}}

Timeout: {{$snmp.Timeout}}

{{ end}}
{{ range $key,$influx := .Influx }}

Influx {{$key}}

Host: {{$influx.URL}}

Database: {{$influx.Database}}

{{/*

Sent: {{$influx.Sent}}

Errors: {{$influx.Errors}}

*/}}

{{ end }}

Profiler

`
)
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"

	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
)

func displayWhatWillChange(rules []Rule) {
	updates := WillUpdateRulesAndTargets(rules)
	deletes := WillDeleteRulesAndTargets(rules)
	if len(updates) == 0 && len(deletes) == 0 {
		fmt.Println("No Changes")
	}
	if len(updates) > 0 {
		fmt.Println("Updates")
		for _, r := range updates {
			ShowWillUpdateFieldInRule(r)
			for _, t := range r.Targets {
				if t.NeedUpdate && !t.NeedDelete {
					ShowWillUpdateFieldInTarget(t)
				}
			}
		}
	}
	if len(deletes) > 0 {
		fmt.Println("Deletes")
		for _, r := range deletes {
			ShowWillDeleteRule(r)
			for _, t := range r.Targets {
				if t.NeedDelete {
					ShowWillDeleteTarget(t)
				}
			}
		}
	}
}

// WillUpdateRulesAndTargets return will be updated rules and targets
func WillUpdateRulesAndTargets(rules []Rule) []Rule {
	u := make([]Rule, 0)
	for _, rule := range rules {
		if rule.NeedUpdate && !rule.NeedDelete {
			u = append(u, rule)
		} else {
			for _, target := range rule.Targets {
				if target.NeedUpdate && !target.NeedDelete {
					u = append(u, rule)
					break
				}
			}
		}
	}
	return u
}

// WillDeleteRulesAndTargets return will be deleted rules and targets
func WillDeleteRulesAndTargets(rules []Rule) []Rule {
	d := make([]Rule, 0)
	for _, rule := range rules {
		if rule.NeedDelete {
			d = append(d, rule)
		} else {
			for _, target := range rule.Targets {
				if target.NeedDelete {
					d = append(d, rule)
					break
				}
			}
		}
	}
	return d
}

// ShowWillUpdateFieldInRule print what will rule changes to stdout
func ShowWillUpdateFieldInRule(rule Rule) {
	fmt.Printf("Rule: %s\n", rule.Name)
	if !CompareString(&rule.Name, rule.ActualRule.Name) {
		fmt.Printf("  Name: %s  ->  %s\n", NilSafeStr(rule.ActualRule.Name), rule.Name)
	}
	if !CompareString(&rule.Description, rule.ActualRule.Description) {
		fmt.Printf("  Description: %s  ->  %s\n", NilSafeStr(rule.ActualRule.Description), rule.Description)
	}
	if !CompareString(&rule.EventPattern, rule.ActualRule.EventPattern) {
		fmt.Printf("  EventPattern: %s  ->  %s\n", NilSafeStr(rule.ActualRule.EventPattern), rule.EventPattern)
	}
	if !CompareString(&rule.RoleArn, rule.ActualRule.RoleArn) {
		fmt.Printf("  RoleArn: %s  ->  %s\n", NilSafeStr(rule.ActualRule.RoleArn), rule.RoleArn)
	}
	if !CompareString(&rule.ScheduleExpression, rule.ActualRule.ScheduleExpression) {
		fmt.Printf("  ScheduleExpression: %s  ->  %s\n", NilSafeStr(rule.ActualRule.ScheduleExpression), rule.ScheduleExpression)
	}
	if !CompareString(&rule.State, rule.ActualRule.State) {
		fmt.Printf("  State: %s  ->  %s\n", NilSafeStr(rule.ActualRule.State), rule.State)
	}
}

// ShowWillUpdateFieldInTarget print what will target changes to stdout
func ShowWillUpdateFieldInTarget(target Target) {
	fmt.Printf("  Target: %s\n", target.Arn)
	if !CompareString(&target.Arn, target.ActualTarget.Arn) {
		fmt.Printf("    Arn: %s  ->  %s\n", NilSafeStr(target.ActualTarget.Arn), target.Arn)
	}
	if !CompareString(&target.Id, target.ActualTarget.Id) {
		fmt.Printf("    Id: %s  ->  %s\n", NilSafeStr(target.ActualTarget.Id), target.Id)
	}
	if !CompareString(&target.Input, target.ActualTarget.Input) {
		fmt.Printf("    Input: %s  ->  %s\n", NilSafeStr(target.ActualTarget.Input), target.Input)
	}
	if !CompareString(&target.InputPath, target.ActualTarget.InputPath) {
		fmt.Printf("    InputPath: %s  ->  %s\n", NilSafeStr(target.ActualTarget.InputPath), target.InputPath)
	}
}

// ShowDiffOfTheEcsParameters print what will EcsParameters in target changes to stdout
func ShowDiffOfTheEcsParameters(current *cloudwatchevents.EcsParameters, expect EcsParameters) {
	var currTaskDefinitionArn, currTaskCount string
	if current != nil {
		currTaskDefinitionArn = *current.TaskDefinitionArn
		currTaskCount = strconv.FormatInt(*current.TaskCount, 10)
	}

	if !CompareString(current.TaskDefinitionArn, &expect.TaskDefinitionArn) {
		fmt.Printf("      TaskDefinitionArn: %s  ->  %s\n", currTaskDefinitionArn, expect.TaskDefinitionArn)
	}

	if !CompareInt64(current.TaskCount, &expect.TaskCount) {
		fmt.Printf("      TaskCount: %s  ->  %d\n", currTaskCount, expect.TaskCount)
	}
}

// ShowDiffOfTheKinesisParameters print what will KinesisParameters in target changes to stdout
func ShowDiffOfTheKinesisParameters(current *cloudwatchevents.KinesisParameters, expect KinesisParameters) {
	var currPart string
	if current != nil {
		currPart = *current.PartitionKeyPath
	}
	fmt.Printf("      PartitionKeyPath: %s  ->  %s\n", currPart, expect.PartitionKeyPath)
}

// ShowWillDeleteRule print the rule will delete to stdout
func ShowWillDeleteRule(rule Rule) {
	if rule.NeedDelete {
		fmt.Printf("Rule: %s this will delete\n", *rule.ActualRule.Name)
	} else {
		fmt.Printf("Rule: %s\n", rule.Name)
	}
}

// ShowWillDeleteTarget print the target will delete to stdout
func ShowWillDeleteTarget(target Target) {
	fmt.Printf("  Target: %s this will delete\n", *target.ActualTarget.Id)
}
<commit_msg>Show diff additionl field of the target<commit_after>package main

import (
	"fmt"
	"strconv"

	"github.com/aws/aws-sdk-go/service/cloudwatchevents"
)

func displayWhatWillChange(rules []Rule) {
	updates := WillUpdateRulesAndTargets(rules)
	deletes := WillDeleteRulesAndTargets(rules)
	if len(updates) == 0 && len(deletes) == 0 {
		fmt.Println("No Changes")
	}
	if len(updates) > 0 {
		fmt.Println("Updates")
		for _, r := range updates {
			ShowWillUpdateFieldInRule(r)
			for _, t := range r.Targets {
				if t.NeedUpdate && !t.NeedDelete {
					ShowWillUpdateFieldInTarget(t)
				}
			}
		}
	}
	if len(deletes) > 0 {
		fmt.Println("Deletes")
		for _, r := range deletes {
			ShowWillDeleteRule(r)
			for _, t := range r.Targets {
				if t.NeedDelete {
					ShowWillDeleteTarget(t)
				}
			}
		}
	}
}

// WillUpdateRulesAndTargets return will be updated rules and targets
func WillUpdateRulesAndTargets(rules []Rule) []Rule {
	u := make([]Rule, 0)
	for _, rule := range rules {
		if rule.NeedUpdate && !rule.NeedDelete {
			u = append(u, rule)
		} else {
			for _, target := range rule.Targets {
				if target.NeedUpdate && !target.NeedDelete {
					u = append(u, rule)
					break
				}
			}
		}
	}
	return u
}

// WillDeleteRulesAndTargets return will be deleted rules and targets
func WillDeleteRulesAndTargets(rules []Rule) []Rule {
	d := make([]Rule, 0)
	for _, rule := range rules {
		if rule.NeedDelete {
			d = append(d, rule)
		} else {
			for _, target := range rule.Targets {
				if target.NeedDelete {
					d = append(d, rule)
					break
				}
			}
		}
	}
	return d
}

// ShowWillUpdateFieldInRule print what will rule changes to stdout
func ShowWillUpdateFieldInRule(rule Rule) {
	fmt.Printf("Rule: %s\n", rule.Name)
	if !CompareString(&rule.Name, rule.ActualRule.Name) {
		fmt.Printf("  Name: %s  ->  %s\n", NilSafeStr(rule.ActualRule.Name), rule.Name)
	}
	if !CompareString(&rule.Description, rule.ActualRule.Description) {
		fmt.Printf("  Description: %s  ->  %s\n", NilSafeStr(rule.ActualRule.Description), rule.Description)
	}
	if !CompareString(&rule.EventPattern, rule.ActualRule.EventPattern) {
		fmt.Printf("  EventPattern: %s  ->  %s\n", NilSafeStr(rule.ActualRule.EventPattern), rule.EventPattern)
	}
	if !CompareString(&rule.RoleArn, rule.ActualRule.RoleArn) {
		fmt.Printf("  RoleArn: %s  ->  %s\n", NilSafeStr(rule.ActualRule.RoleArn), rule.RoleArn)
	}
	if !CompareString(&rule.ScheduleExpression, rule.ActualRule.ScheduleExpression) {
		fmt.Printf("  ScheduleExpression: %s  ->  %s\n", NilSafeStr(rule.ActualRule.ScheduleExpression), rule.ScheduleExpression)
	}
	if !CompareString(&rule.State, rule.ActualRule.State) {
		fmt.Printf("  State: %s  ->  %s\n", NilSafeStr(rule.ActualRule.State), rule.State)
	}
}

// ShowWillUpdateFieldInTarget print what will target changes to stdout
func ShowWillUpdateFieldInTarget(target Target) {
	fmt.Printf("  Target: %s\n", target.Arn)
	if !CompareString(&target.Arn, target.ActualTarget.Arn) {
		fmt.Printf("    Arn: %s  ->  %s\n", NilSafeStr(target.ActualTarget.Arn), target.Arn)
	}
	if !CompareString(&target.Id, target.ActualTarget.Id) {
		fmt.Printf("    Id: %s  ->  %s\n", NilSafeStr(target.ActualTarget.Id), target.Id)
	}
	if !CompareString(&target.Input, target.ActualTarget.Input) {
		fmt.Printf("    Input: %s  ->  %s\n", NilSafeStr(target.ActualTarget.Input), target.Input)
	}
	if !CompareString(&target.InputPath, target.ActualTarget.InputPath) {
		fmt.Printf("    InputPath: %s  ->  %s\n", NilSafeStr(target.ActualTarget.InputPath), target.InputPath)
	}
	if !CompareString(&target.RoleArn, target.ActualTarget.RoleArn) {
		fmt.Printf("    RoleArn: %s  ->  %s\n", NilSafeStr(target.ActualTarget.RoleArn), target.RoleArn)
	}
	if !CompareEcsParameters(&target.EcsParameters, target.ActualTarget.EcsParameters) {
		fmt.Println("    EcsParameters:")
		ShowDiffOfTheEcsParameters(target.ActualTarget.EcsParameters, target.EcsParameters)
	}
	if !CompareKinesisParameters(&target.KinesisParameters, target.ActualTarget.KinesisParameters) {
		fmt.Println("    KinesisParameters:")
		ShowDiffOfTheKinesisParameters(target.ActualTarget.KinesisParameters, target.KinesisParameters)
	}
}

// ShowDiffOfTheEcsParameters print what will EcsParameters in target changes to stdout
func ShowDiffOfTheEcsParameters(current *cloudwatchevents.EcsParameters, expect EcsParameters) {
	var currTaskDefinitionArn, currTaskCount string
	if current != nil {
		currTaskDefinitionArn = *current.TaskDefinitionArn
		currTaskCount = strconv.FormatInt(*current.TaskCount, 10)
	}

	if !CompareString(current.TaskDefinitionArn, &expect.TaskDefinitionArn) {
		fmt.Printf("      TaskDefinitionArn: %s  ->  %s\n", currTaskDefinitionArn, expect.TaskDefinitionArn)
	}

	if !CompareInt64(current.TaskCount, &expect.TaskCount) {
		fmt.Printf("      TaskCount: %s  ->  %d\n", currTaskCount, expect.TaskCount)
	}
}

// ShowDiffOfTheKinesisParameters print what will KinesisParameters in target changes to stdout
func ShowDiffOfTheKinesisParameters(current *cloudwatchevents.KinesisParameters, expect KinesisParameters) {
	var currPart string
	if current != nil {
		currPart = *current.PartitionKeyPath
	}
	fmt.Printf("      PartitionKeyPath: %s  ->  %s\n", currPart, expect.PartitionKeyPath)
}

// ShowWillDeleteRule print the rule will delete to stdout
func ShowWillDeleteRule(rule Rule) {
	if rule.NeedDelete {
		fmt.Printf("Rule: %s this will delete\n", *rule.ActualRule.Name)
	} else {
		fmt.Printf("Rule: %s\n", rule.Name)
	}
}

// ShowWillDeleteTarget print the target will delete to stdout
func ShowWillDeleteTarget(target Target) {
	fmt.Printf("  Target: %s this will delete\n", *target.ActualTarget.Id)
}
<|endoftext|>