<commit_before>package storage

import "fmt"
import "time"
import "launchpad.net/gozk/zookeeper"

/**
 * ZooKeeper Storage Manager
 *
 * @author: Anant Bhardwaj
 * @date: 05/11/2014
 */

type StorageManager struct {
  conn *zookeeper.Conn
}

func MakeStorageManager() *StorageManager {
  sm := &StorageManager{}
  return sm
}

func (sm *StorageManager) Open(servers string) error {
  conn, session, err := zookeeper.Dial(servers, 5 * time.Second)
  if err != nil {
    fmt.Printf("Can't connect to zookeeper: %v\n", err)
    return err
  }

  // Wait for connection.
  event := <-session
  if event.State != zookeeper.STATE_CONNECTED {
    fmt.Printf("Can't connect to zookeeper: %v\n", event)
    return err
  }

  sm.conn = conn
  return nil
}

func (sm *StorageManager) Close() error {
  return sm.conn.Close()
}

func (sm *StorageManager) Create(path string, data string) error {
  stats, _ := sm.conn.Exists(path)
  var err error

  if stats == nil {
    _, err = sm.conn.Create(path, data, 0, zookeeper.WorldACL(zookeeper.PERM_ALL))
  }

  if err != nil {
    fmt.Printf("Error creating a node (%v): %v\n", path, err)
  }

  return err
}

func (sm *StorageManager) Delete(path string) error {

  err := sm.conn.Delete(path, -1)

  if err != nil {
    fmt.Printf("Error writing to node (%v): %v\n", path, err)
  }

  return err
}

func (sm *StorageManager) Write(path string, data string) error {

  _, err := sm.conn.Set(path, data, -1)

  if err != nil {
    err_create := sm.Create(path, data)
    if err_create != nil {
      fmt.Printf("Error writing to node (%v): %v\n", path, err)
      return err
    }
  }
}

func (sm *StorageManager) Read(path string) (string, error) {
  data, _, err := sm.conn.Get(path)

  if err != nil {
    fmt.Printf("Error reading from node (%v): %v\n", path, err)
  }

  return data, err
}
<commit_msg>persistant paxos<commit_after>package storage

import "fmt"
import "time"
import "launchpad.net/gozk/zookeeper"

/**
 * ZooKeeper Storage Manager
 *
 * @author: Anant Bhardwaj
 * @date: 05/11/2014
 */

type StorageManager struct {
  conn *zookeeper.Conn
}

func MakeStorageManager() *StorageManager {
  sm := &StorageManager{}
  return sm
}

func (sm *StorageManager) Open(servers string) error {
  conn, session, err := zookeeper.Dial(servers, 5 * time.Second)
  if err != nil {
    fmt.Printf("Can't connect to zookeeper: %v\n", err)
    return err
  }

  // Wait for connection.
  event := <-session
  if event.State != zookeeper.STATE_CONNECTED {
    fmt.Printf("Can't connect to zookeeper: %v\n", event)
    return err
  }

  sm.conn = conn
  return nil
}

func (sm *StorageManager) Close() error {
  return sm.conn.Close()
}

func (sm *StorageManager) Create(path string, data string) error {
  stats, _ := sm.conn.Exists(path)
  var err error

  if stats == nil {
    _, err = sm.conn.Create(path, data, 0, zookeeper.WorldACL(zookeeper.PERM_ALL))
  }

  if err != nil {
    fmt.Printf("Error creating a node (%v): %v\n", path, err)
  }

  return err
}

func (sm *StorageManager) Delete(path string) error {

  err := sm.conn.Delete(path, -1)

  if err != nil {
    fmt.Printf("Error writing to node (%v): %v\n", path, err)
  }

  return err
}

func (sm *StorageManager) Write(path string, data string) error {

  _, err := sm.conn.Set(path, data, -1)

  if err != nil {
    err_create := sm.Create(path, data)
    if err_create != nil {
      fmt.Printf("Error writing to node (%v): %v\n", path, err)
      return err_create
    }
  }
  return err
}

func (sm *StorageManager) Read(path string) (string, error) {
  data, _, err := sm.conn.Get(path)

  if err != nil {
    fmt.Printf("Error reading from node (%v): %v\n", path, err)
  }

  return data, err
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"strings"

	"github.com/bitly/go-simplejson"
	"github.com/leancloud/lean-cli/lean/version"
	"github.com/levigross/grequests"
)

const (
	hostCN = "https://api.leancloud.cn"
	hostUS = "https://us-api.leancloud.cn"
)

// API server regions
const (
	RegionInvalid = iota
	RegionCN
	RegionUS
)

// Client info
type Client struct {
}

// NewClient initilized a new Client
func NewClient() *Client {
	return &Client{}
}

func (client *Client) fetchRouter() error {
	// TODO: fetch router from server
	return nil
}

func (client *Client) baseURL() string {
	// TODO: return base URL per region
	return hostCN
}

func (client *Client) options() (*grequests.RequestOptions, error) {
	cookies, err := getCookies()
	if err != nil {
		return nil, ErrNotLogined
	}

	xsrfTok := ""
	for _, cookie := range cookies {
		if cookie.Name == "XSRF-TOKEN" {
			xsrfTok = cookie.Value
			break
		}
	}

	return &grequests.RequestOptions{
		Cookies: cookies,
		Headers: map[string]string{
			"X-XSRF-TOKEN": xsrfTok,
		},
		UserAgent: "LeanCloud-CLI/v" + version.Version,
	}, nil
}

func (client *Client) get(path string, options *grequests.RequestOptions) (*simplejson.Json, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	resp, err := grequests.Get(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}

	return simplejson.NewFromReader(resp)
}

func (client *Client) getX(path string, options *grequests.RequestOptions) (*grequests.Response, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	resp, err := grequests.Get(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
			return nil, NewErrorFromBody(resp.String())
		}
		return nil, fmt.Errorf("HTTP Error: %d", resp.StatusCode)
	}

	return resp, nil
}

func (client *Client) post(path string, params map[string]interface{}, options *grequests.RequestOptions) (*simplejson.Json, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	options.JSON = params
	resp, err := grequests.Post(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}
	return simplejson.NewFromReader(resp)
}

func (client *Client) postX(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	options.JSON = params
	resp, err := grequests.Post(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}
	return resp, nil
}

func (client *Client) delete(path string, options *grequests.RequestOptions) (*simplejson.Json, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	resp, err := grequests.Delete(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}
	return simplejson.NewFromReader(resp)
}
<commit_msg>fix: invalid api user agent string format<commit_after>package api

import (
	"fmt"
	"strings"

	"github.com/bitly/go-simplejson"
	"github.com/leancloud/lean-cli/lean/version"
	"github.com/levigross/grequests"
)

const (
	hostCN = "https://api.leancloud.cn"
	hostUS = "https://us-api.leancloud.cn"
)

// API server regions
const (
	RegionInvalid = iota
	RegionCN
	RegionUS
)

// Client info
type Client struct {
}

// NewClient initilized a new Client
func NewClient() *Client {
	return &Client{}
}

func (client *Client) fetchRouter() error {
	// TODO: fetch router from server
	return nil
}

func (client *Client) baseURL() string {
	// TODO: return base URL per region
	return hostCN
}

func (client *Client) options() (*grequests.RequestOptions, error) {
	cookies, err := getCookies()
	if err != nil {
		return nil, ErrNotLogined
	}

	xsrfTok := ""
	for _, cookie := range cookies {
		if cookie.Name == "XSRF-TOKEN" {
			xsrfTok = cookie.Value
			break
		}
	}

	return &grequests.RequestOptions{
		Cookies: cookies,
		Headers: map[string]string{
			"X-XSRF-TOKEN": xsrfTok,
		},
		UserAgent: "LeanCloud-CLI/" + version.Version,
	}, nil
}

func (client *Client) get(path string, options *grequests.RequestOptions) (*simplejson.Json, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	resp, err := grequests.Get(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}

	return simplejson.NewFromReader(resp)
}

func (client *Client) getX(path string, options *grequests.RequestOptions) (*grequests.Response, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	resp, err := grequests.Get(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
			return nil, NewErrorFromBody(resp.String())
		}
		return nil, fmt.Errorf("HTTP Error: %d", resp.StatusCode)
	}

	return resp, nil
}

func (client *Client) post(path string, params map[string]interface{}, options *grequests.RequestOptions) (*simplejson.Json, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	options.JSON = params
	resp, err := grequests.Post(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}
	return simplejson.NewFromReader(resp)
}

func (client *Client) postX(path string, params map[string]interface{}, options *grequests.RequestOptions) (*grequests.Response, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	options.JSON = params
	resp, err := grequests.Post(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}
	return resp, nil
}

func (client *Client) delete(path string, options *grequests.RequestOptions) (*simplejson.Json, error) {
	var err error
	if options == nil {
		if options, err = client.options(); err != nil {
			return nil, err
		}
	}
	resp, err := grequests.Delete(client.baseURL()+path, options)
	if err != nil {
		return nil, err
	}
	if !resp.Ok {
		return nil, NewErrorFromBody(resp.String())
	}
	return simplejson.NewFromReader(resp)
}
<|endoftext|>


<commit_before>package genetics

import (
	"github.com/yaricom/goNEAT/neat/network"
	"fmt"
)

// Organisms are Genomes and Networks with fitness information,
// i.e. the genotype and phenotype together.
type Organism struct {
	// A measure of fitness for the Organism
	Fitness                   float64
	// A fitness measure that won't change during adjustments
	OriginalFitness           float64

	// Win marker (if needed for a particular task)
	IsWinner                  bool

	// The Organism's phenotype
	Net                       *network.Network
	// The Organism's genotype
	GNome                     *Genome
	// The Species of the Organism
	SpeciesOf                 *Species

	// Number of children this Organism may have
	ExpectedOffspring         float64
	// Tells which generation this Organism is from
	Generation                int

	// Marker for destruction of inferior Organisms
	ToEliminate               bool
	// Marks the species champion
	IsChampion                bool

	// Number of reserved offspring for a population leader
	SuperChampOffspring       int
	// Marks the best in population
	IsPopulationChampion      bool
	// Marks the duplicate child of a champion (for tracking purposes)
	IsPopulationChampionChild bool

	// DEBUG variable - highest fitness of champ
	highestFitness            float64

	// Track its origin - for debugging or analysis - we can tell how the organism was born
	mutationStructBaby        bool
	mateBaby                  bool

	// Used just for reporting purposes
	error float64
}

// Creates new organism with specified genome, fitness and given generation number
func NewOrganism(fit float64, g *Genome, generation int) *Organism {
	return &Organism{
		Fitness:fit,
		GNome:g,
		Net:g.genesis(g.Id),
		Generation:generation,
	}
}

// Regenerate the network based on a change in the genotype
func (o *Organism) UpdatePhenotype() {
	// First, delete the old phenotype (net)
	o.Net = nil

	// Now, recreate the phenotype off the new genotype
	o.Net = o.GNome.genesis(o.GNome.Id)
}

func (o *Organism) String() string {
	champStr := ""
	if o.IsChampion {
		champStr = " - CHAMPION - "
	}
	eliminStr := ""
	if o.ToEliminate {
		eliminStr = " - TO BE ELIMINATED - "
	}
	return fmt.Sprintf("[Organism generation: %d, fitness: %.3f, original fitness: %.3f%s%s]",
		o.Generation, o.Fitness, o.OriginalFitness, champStr, eliminStr)
}

// ByFitness implements sort.Interface for []Organism based on the Fitness field in descending order.
type ByFitness []*Organism
func (f ByFitness) Len() int {
	return len(f)
}
func (f ByFitness) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}
func (f ByFitness) Less(i, j int) bool {
	return f[i].Fitness > f[j].Fitness
}
<commit_msg>Removed obsolete call for genome genesis<commit_after>package genetics

import (
	"github.com/yaricom/goNEAT/neat/network"
	"fmt"
)

// Organisms are Genomes and Networks with fitness information,
// i.e. the genotype and phenotype together.
type Organism struct {
	// A measure of fitness for the Organism
	Fitness                   float64
	// A fitness measure that won't change during adjustments
	OriginalFitness           float64

	// Win marker (if needed for a particular task)
	IsWinner                  bool

	// The Organism's phenotype
	Net                       *network.Network
	// The Organism's genotype
	GNome                     *Genome
	// The Species of the Organism
	SpeciesOf                 *Species

	// Number of children this Organism may have
	ExpectedOffspring         float64
	// Tells which generation this Organism is from
	Generation                int

	// Marker for destruction of inferior Organisms
	ToEliminate               bool
	// Marks the species champion
	IsChampion                bool

	// Number of reserved offspring for a population leader
	SuperChampOffspring       int
	// Marks the best in population
	IsPopulationChampion      bool
	// Marks the duplicate child of a champion (for tracking purposes)
	IsPopulationChampionChild bool

	// DEBUG variable - highest fitness of champ
	highestFitness            float64

	// Track its origin - for debugging or analysis - we can tell how the organism was born
	mutationStructBaby        bool
	mateBaby                  bool

	// Used just for reporting purposes
	error float64
}

// Creates new organism with specified genome, fitness and given generation number
func NewOrganism(fit float64, g *Genome, generation int) *Organism {
	return &Organism{
		Fitness:fit,
		GNome:g,
		Net:g.Phenotype, // TODO g.genessis(g.Id)
		Generation:generation,
	}
}

// Regenerate the network based on a change in the genotype
func (o *Organism) UpdatePhenotype() {
	// First, delete the old phenotype (net)
	o.Net = nil

	// Now, recreate the phenotype off the new genotype
	o.Net = o.GNome.genesis(o.GNome.Id)
}

func (o *Organism) String() string {
	champStr := ""
	if o.IsChampion {
		champStr = " - CHAMPION - "
	}
	eliminStr := ""
	if o.ToEliminate {
		eliminStr = " - TO BE ELIMINATED - "
	}
	return fmt.Sprintf("[Organism generation: %d, fitness: %.3f, original fitness: %.3f%s%s]",
		o.Generation, o.Fitness, o.OriginalFitness, champStr, eliminStr)
}

// ByFitness implements sort.Interface for []Organism based on the Fitness field in descending order.
type ByFitness []*Organism
func (f ByFitness) Len() int {
	return len(f)
}
func (f ByFitness) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}
func (f ByFitness) Less(i, j int) bool {
	return f[i].Fitness > f[j].Fitness
}
<|endoftext|>


<commit_before>package boot

import (
	"bytes"
	"encoding/binary"
	"fmt"

	"github.com/samofly/crazyradio"
	"github.com/samofly/crazyradio/usb"
)

const BootloaderChannel = 110

const (
	CMD_GET_INFO     = 0x10
	CMD_LOAD_BUFFER  = 0x14
	CMD_READ_BUFFER  = 0x15
	CMD_WRITE_FLASH  = 0x18
	CMD_FLASH_STATUS = 0x19
	CMD_READ_FLASH   = 0x1C

	PageSize = 1024

	ConfigPageIndex = 127

	CpuIdLen = 12
)

type wireInfo struct {
	PageSize    uint16
	BufferPages uint16
	FlashPages  uint16
	FlashStart  uint16
	CpuId       [CpuIdLen]byte
	Version     byte
}

type Info struct {
	PageSize    int
	BufferPages int
	FlashPages  int
	FlashStart  int
	CpuId       []byte
	Version     int
}

// Cold waits for a Crazyflie startup and connects to its bootloader.
func Cold() (dev crazyradio.Device, info Info, err error) {
	buf := make([]byte, 128)
	dev, err = usb.OpenAny()
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			dev.Close()
			dev = nil
		}
	}()
	err = dev.SetRateAndChannel(crazyradio.DATA_RATE_2M, BootloaderChannel)
	if err != nil {
		err = fmt.Errorf("SetRateAndChannel: %v", err)
		return
	}

	for {
		_, err = dev.Write([]byte{0xFF, 0xFF, 0x10})
		if err != nil {
			continue
		}
		n, err := dev.Read(buf)
		if err != nil {
			continue
		}
		if n < 4 || buf[3] != CMD_GET_INFO {
			continue
		}
		// Try to parse info
		var wi wireInfo
		err = binary.Read(bytes.NewBuffer(buf[4:n]), binary.LittleEndian, &wi)
		if err != nil {
			continue
		}
		info = Info{
			PageSize:    int(wi.PageSize),
			BufferPages: int(wi.BufferPages),
			FlashPages:  int(wi.FlashPages),
			FlashStart:  int(wi.FlashStart),
			CpuId:       wi.CpuId[:],
			Version:     int(wi.Version),
		}
		// We're connected!
		break
	}

	if info.PageSize != PageSize {
		err = fmt.Errorf("Unsupported page size: %d. This utility only supports PageSize=%d",
			info.PageSize, PageSize)
		return
	}
	return
}
<commit_msg>Set radio address in boot.Cold. It works, but now there's a real need to restart copter, once we're done with the bootloader<commit_after>package boot

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"time"

	"github.com/samofly/crazyradio"
	"github.com/samofly/crazyradio/usb"
)

const BootloaderChannel = 110

const (
	CMD_GET_INFO     = 0x10
	CMD_SET_ADDRESS  = 0x11
	CMD_LOAD_BUFFER  = 0x14
	CMD_READ_BUFFER  = 0x15
	CMD_WRITE_FLASH  = 0x18
	CMD_FLASH_STATUS = 0x19
	CMD_READ_FLASH   = 0x1C

	PageSize = 1024

	ConfigPageIndex = 127

	CpuIdLen = 12
)

type wireInfo struct {
	PageSize    uint16
	BufferPages uint16
	FlashPages  uint16
	FlashStart  uint16
	CpuId       [CpuIdLen]byte
	Version     byte
}

type Info struct {
	PageSize    int
	BufferPages int
	FlashPages  int
	FlashStart  int
	CpuId       []byte
	Version     int
}

func setRadioAddress(addr [5]byte) (p []byte) {
	return []byte{0xFF, 0xFF, CMD_SET_ADDRESS, addr[0], addr[1], addr[2], addr[3], addr[4]}
}

// Cold waits for a Crazyflie startup and connects to its bootloader.
func Cold() (dev crazyradio.Device, info Info, err error) {
	buf := make([]byte, 128)
	dev, err = usb.OpenAny()
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			dev.Close()
			dev = nil
		}
	}()
	err = dev.SetRateAndChannel(crazyradio.DATA_RATE_2M, BootloaderChannel)
	if err != nil {
		err = fmt.Errorf("SetRateAndChannel: %v", err)
		return
	}

	for {
		_, err = dev.Write([]byte{0xFF, 0xFF, 0x10})
		if err != nil {
			continue
		}
		n, err := dev.Read(buf)
		if err != nil {
			continue
		}
		if n < 4 || buf[3] != CMD_GET_INFO {
			continue
		}
		// Try to parse info
		var wi wireInfo
		err = binary.Read(bytes.NewBuffer(buf[4:n]), binary.LittleEndian, &wi)
		if err != nil {
			continue
		}
		info = Info{
			PageSize:    int(wi.PageSize),
			BufferPages: int(wi.BufferPages),
			FlashPages:  int(wi.FlashPages),
			FlashStart:  int(wi.FlashStart),
			CpuId:       wi.CpuId[:],
			Version:     int(wi.Version),
		}
		// We're connected!
		break
	}

	if info.PageSize != PageSize {
		err = fmt.Errorf("Unsupported page size: %d. This utility only supports PageSize=%d",
			info.PageSize, PageSize)
		return
	}

	// Now, we need to send CMD_SET_ADDRESS to get away from the default one
	// This will ensure that even if there's more than one Crazyflies are being updated,
	// they will behave correctly (though, probably, slower, since channel and rate are still
	// the same
	sec := time.Now().Second()
	nano := time.Now().Nanosecond()
	addr := [5]byte{byte(sec), byte(nano & 0xFF), byte((nano >> 8) & 0xFF),
		byte((nano >> 16)) & 0xFF, byte((nano >> 24) & 0xFF)}
	ok := false
	for try := 0; try < 10; try++ {
		_, err = dev.Write(setRadioAddress(addr))
		if err != nil {
			log.Printf("CMD_SET_ADDRESS: %v", err)
			continue
		}
		ok = true
	}
	if !ok {
		err = fmt.Errorf("Failed to send CMD_SET_ADDRESS: %v", err)
		return
	}
	err = dev.SetRadioAddress(addr)
	return
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package lifecycle

import (
	"context"
	"fmt"
	"io"
	"time"

	"k8s.io/klog/v2"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilcache "k8s.io/apimachinery/pkg/util/cache"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/initializer"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/utils/clock"
)

const (
	// PluginName indicates the name of admission plug-in
	PluginName = "NamespaceLifecycle"
	// how long a namespace stays in the force live lookup cache before expiration.
	forceLiveLookupTTL = 30 * time.Second
	// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
	// this accomplishes two things:
	// 1. It allows a watch-fed cache time to observe a namespace creation event
	// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
	//    so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
	missingNamespaceWait = 50 * time.Millisecond
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
		return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
	})
}

// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
	*admission.Handler
	client             kubernetes.Interface
	immortalNamespaces sets.String
	namespaceLister    corelisters.NamespaceLister
	// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
	// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
	forceLiveLookupCache *utilcache.LRUExpireCache
}

var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})

// Admit makes an admission decision based on the request attributes
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
		return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
	}

	// always allow non-namespaced resources
	if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		return nil
	}

	if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		// if a namespace is deleted, we want to prevent all further creates into it
		// while it is undergoing termination.  to reduce incidences where the cache
		// is slow to update, we add the namespace into a force live lookup list to ensure
		// we are not looking at stale state.
		if a.GetOperation() == admission.Delete {
			l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
		}
		// allow all operations to namespaces
		return nil
	}

	// always allow deletion of other resources
	if a.GetOperation() == admission.Delete {
		return nil
	}

	// always allow access review checks.  Returning status about the namespace would be leaking information
	if isAccessReview(a) {
		return nil
	}

	// we need to wait for our caches to warm
	if !l.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	var (
		exists bool
		err    error
	)

	namespace, err := l.namespaceLister.Get(a.GetNamespace())
	if err != nil {
		if !errors.IsNotFound(err) {
			return errors.NewInternalError(err)
		}
	} else {
		exists = true
	}

	if !exists && a.GetOperation() == admission.Create {
		// give the cache time to observe the namespace before rejecting a create.
		// this helps when creating a namespace and immediately creating objects within it.
		time.Sleep(missingNamespaceWait)
		namespace, err = l.namespaceLister.Get(a.GetNamespace())
		switch {
		case errors.IsNotFound(err):
			// no-op
		case err != nil:
			return errors.NewInternalError(err)
		default:
			exists = true
		}
		if exists {
			klog.V(4).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", a.GetNamespace()))
		}
	}

	// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
	forceLiveLookup := false
	if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
		// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
		forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
	}

	// refuse to operate on non-existent namespaces
	if !exists || forceLiveLookup {
		// as a last resort, make a call directly to storage
		namespace, err = l.client.CoreV1().Namespaces().Get(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
		switch {
		case errors.IsNotFound(err):
			return err
		case err != nil:
			return errors.NewInternalError(err)
		}

		klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", a.GetNamespace()))
	}

	// ensure that we're not trying to create objects in terminating namespaces
	if a.GetOperation() == admission.Create {
		if namespace.Status.Phase != v1.NamespaceTerminating {
			return nil
		}

		err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
		if apierr, ok := err.(*errors.StatusError); ok {
			apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
				Type:    v1.NamespaceTerminatingCause,
				Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
				Field:   "metadata.namespace",
			})
		}
		return err
	}

	return nil
}

// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
	return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}

func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
	forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
	return &Lifecycle{
		Handler:              admission.NewHandler(admission.Create, admission.Update, admission.Delete),
		immortalNamespaces:   immortalNamespaces,
		forceLiveLookupCache: forceLiveLookupCache,
	}, nil
}

// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
	namespaceInformer := f.Core().V1().Namespaces()
	l.namespaceLister = namespaceInformer.Lister()
	l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}

// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
	l.client = client
}

// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
	if l.namespaceLister == nil {
		return fmt.Errorf("missing namespaceLister")
	}
	if l.client == nil {
		return fmt.Errorf("missing client")
	}
	return nil
}

// accessReviewResources are resources which give a view into permissions in a namespace.  Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
}

func isAccessReview(a admission.Attributes) bool {
	return accessReviewResources[a.GetResource().GroupResource()]
}
<commit_msg>UPSTREAM: <carry>: kube-apiserver: add our immortal namespaces directly to admission plugin<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package lifecycle

import (
	"context"
	"fmt"
	"io"
	"time"

	"k8s.io/klog/v2"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilcache "k8s.io/apimachinery/pkg/util/cache"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/admission/initializer"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/utils/clock"
)

const (
	// PluginName indicates the name of admission plug-in
	PluginName = "NamespaceLifecycle"
	// how long a namespace stays in the force live lookup cache before expiration.
	forceLiveLookupTTL = 30 * time.Second
	// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
	// this accomplishes two things:
	// 1. It allows a watch-fed cache time to observe a namespace creation event
	// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
	//    so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
	missingNamespaceWait = 50 * time.Millisecond
)

// Register registers a plugin
func Register(plugins *admission.Plugins) {
	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
		return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic,
			// user specified configuration that cannot be rebuilt
			"openshift-config",
			// cluster generated configuration that cannot be rebuilt (etcd encryption keys)
			"openshift-config-managed",
			// the CVO which is the root we use to rebuild all the rest
			"openshift-cluster-version",
			// contains a namespaced list of all nodes in the cluster (yeah, weird.  they do it for multi-tenant management I think?)
			"openshift-machine-api",
		))
	})
}

// Lifecycle is an implementation of admission.Interface.
// It enforces life-cycle constraints around a Namespace depending on its Phase
type Lifecycle struct {
	*admission.Handler
	client             kubernetes.Interface
	immortalNamespaces sets.String
	namespaceLister    corelisters.NamespaceLister
	// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
	// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
	forceLiveLookupCache *utilcache.LRUExpireCache
}

var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})

// Admit makes an admission decision based on the request attributes
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
		return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
	}

	// always allow non-namespaced resources
	if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		return nil
	}

	if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
		// if a namespace is deleted, we want to prevent all further creates into it
		// while it is undergoing termination.  to reduce incidences where the cache
		// is slow to update, we add the namespace into a force live lookup list to ensure
		// we are not looking at stale state.
		if a.GetOperation() == admission.Delete {
			l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
		}
		// allow all operations to namespaces
		return nil
	}

	// always allow deletion of other resources
	if a.GetOperation() == admission.Delete {
		return nil
	}

	// always allow access review checks.  Returning status about the namespace would be leaking information
	if isAccessReview(a) {
		return nil
	}

	// we need to wait for our caches to warm
	if !l.WaitForReady() {
		return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
	}

	var (
		exists bool
		err    error
	)

	namespace, err := l.namespaceLister.Get(a.GetNamespace())
	if err != nil {
		if !errors.IsNotFound(err) {
			return errors.NewInternalError(err)
		}
	} else {
		exists = true
	}

	if !exists && a.GetOperation() == admission.Create {
		// give the cache time to observe the namespace before rejecting a create.
		// this helps when creating a namespace and immediately creating objects within it.
		time.Sleep(missingNamespaceWait)
		namespace, err = l.namespaceLister.Get(a.GetNamespace())
		switch {
		case errors.IsNotFound(err):
			// no-op
		case err != nil:
			return errors.NewInternalError(err)
		default:
			exists = true
		}
		if exists {
			klog.V(4).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", a.GetNamespace()))
		}
	}

	// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
	forceLiveLookup := false
	if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
		// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
		forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
	}

	// refuse to operate on non-existent namespaces
	if !exists || forceLiveLookup {
		// as a last resort, make a call directly to storage
		namespace, err = l.client.CoreV1().Namespaces().Get(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
		switch {
		case errors.IsNotFound(err):
			return err
		case err != nil:
			return errors.NewInternalError(err)
		}

		klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", a.GetNamespace()))
	}

	// ensure that we're not trying to create objects in terminating namespaces
	if a.GetOperation() == admission.Create {
		if namespace.Status.Phase != v1.NamespaceTerminating {
			return nil
		}

		err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
		if apierr, ok := err.(*errors.StatusError); ok {
			apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
				Type:    v1.NamespaceTerminatingCause,
				Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
				Field:   "metadata.namespace",
			})
		}
		return err
	}

	return nil
}

// NewLifecycle creates a new namespace Lifecycle admission control handler
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
	return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
}

func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
	forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
	return &Lifecycle{
		Handler:              admission.NewHandler(admission.Create, admission.Update, admission.Delete),
		immortalNamespaces:   immortalNamespaces,
		forceLiveLookupCache: forceLiveLookupCache,
	}, nil
}

// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
	namespaceInformer := f.Core().V1().Namespaces()
	l.namespaceLister = namespaceInformer.Lister()
	l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
}

// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
	l.client = client
}

// ValidateInitialization implements the InitializationValidator interface.
func (l *Lifecycle) ValidateInitialization() error {
	if l.namespaceLister == nil {
		return fmt.Errorf("missing namespaceLister")
	}
	if l.client == nil {
		return fmt.Errorf("missing client")
	}
	return nil
}

// accessReviewResources are resources which give a view into permissions in a namespace.  Users must be allowed to create these
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
var accessReviewResources = map[schema.GroupResource]bool{
	{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
}

func isAccessReview(a admission.Attributes) bool {
	return accessReviewResources[a.GetResource().GroupResource()]
}
<|endoftext|>


<commit_before>package projects

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"

	log "github.com/Sirupsen/logrus"
	"github.com/citruspi/milou/configuration"
	"github.com/citruspi/milou/notifications"
)

type Project struct {
	Owner      string `json:"owner"`
	Repository string `json:"repository"`
	Version    struct {
		Type  string `json:"type"`
		Value string `json:"value"`
		Regex bool   `json:"regex"`
	} `json:"version"`
	Identifier string `json:"identifier"`
	Path       string `json:"path"`
}

var (
	conf configuration.Configuration
	list []Project
)

func init() {
	conf = configuration.Load()

	files, err := ioutil.ReadDir(conf.Projects)

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

	for _, file := range files {
		var project Project

		source, err := ioutil.ReadFile(conf.Projects + file.Name())

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

		err = json.Unmarshal(source, &project)

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

		list = append(list, project)
	}

	if conf.Mode == "standalone" || conf.Mode == "client" {
		DeployAll()
	}
}

func (p Project) BasePath() string {
	buffer := new(bytes.Buffer)

	t, _ := template.New("basepath").Parse(p.Path)
	t.Execute(buffer, p)

	return string(buffer.Bytes())
}

func (p Project) ArchivePath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Version.Value)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) TemporaryPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.BasePath()[:len(p.BasePath())-1])
	buffer.WriteString(".milou/")

	return string(buffer.Bytes())
}

func (p Project) ExtractPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Repository)

	return string(buffer.Bytes())
}

func (p Project) ArchiveLocation() string {
	var buffer bytes.Buffer

	buffer.WriteString("https://s3.amazonaws.com/")
	buffer.WriteString(p.Identifier)
	buffer.WriteString("/")
	buffer.WriteString(p.Version.Value)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) Extract() error {
	r, err := zip.OpenReader(p.ArchivePath())
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		fpath := filepath.Join(p.ExtractPath(), f.Name)
		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, f.Mode())
		} else {
			var fdir string
			if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
				fdir = fpath[:lastIndex]
			}

			err = os.MkdirAll(fdir, f.Mode())
			if err != nil {
				log.Fatal(err)
				return err
			}
			f, err := os.OpenFile(
				fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			defer f.Close()

			_, err = io.Copy(f, rc)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (p Project) Download() {
	response, err := http.Get(p.ArchiveLocation())
	defer response.Body.Close()

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

	archive, err := os.Create(p.ArchivePath())
	defer archive.Close()

	_, err = io.Copy(archive, response.Body)

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

func (p Project) Place() {
	err := os.RemoveAll(p.BasePath())

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

	err = os.Rename(p.ExtractPath(), p.BasePath())

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

func (p Project) Prepare() {
	if _, err := os.Stat(p.TemporaryPath()); os.IsNotExist(err) {
		err = os.MkdirAll(p.TemporaryPath(), 0700)

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

func (p Project) CleanUp() {
	err := os.RemoveAll(p.TemporaryPath())

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

func (p Project) Deploy() {
	p.Prepare()
	p.Download()
	p.Extract()
	p.Place()
	p.CleanUp()
}

func DeployAll() {
	for _, project := range list {
		project.Deploy()
	}
}

func Process(n notifications.Notification) {
	for _, project := range list {
		if project.Repository != n.Repository {
			continue
		}

		if project.Owner != n.Owner {
			continue
		}

		if project.Version.Type == "commit" {
			if project.Version.Value == n.Commit {
				project.Deploy()
				continue
			}
		}

		if project.Version.Type == "branch" {
			if project.Version.Regex {
				match, _ := regexp.MatchString(project.Version.Value, n.Branch)

				if match {
					project.Deploy()
				}
			} else if project.Version.Value == n.Branch {
				project.Deploy()
			}
		}
	}
}
<commit_msg>Created the project's extraction path before attempting to extract it<commit_after>package projects

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"

	log "github.com/Sirupsen/logrus"
	"github.com/citruspi/milou/configuration"
	"github.com/citruspi/milou/notifications"
)

type Project struct {
	Owner      string `json:"owner"`
	Repository string `json:"repository"`
	Version    struct {
		Type  string `json:"type"`
		Value string `json:"value"`
		Regex bool   `json:"regex"`
	} `json:"version"`
	Identifier string `json:"identifier"`
	Path       string `json:"path"`
}

var (
	conf configuration.Configuration
	list []Project
)

func init() {
	conf = configuration.Load()

	files, err := ioutil.ReadDir(conf.Projects)

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

	for _, file := range files {
		var project Project

		source, err := ioutil.ReadFile(conf.Projects + file.Name())

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

		err = json.Unmarshal(source, &project)

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

		list = append(list, project)
	}

	if conf.Mode == "standalone" || conf.Mode == "client" {
		DeployAll()
	}
}

func (p Project) BasePath() string {
	buffer := new(bytes.Buffer)

	t, _ := template.New("basepath").Parse(p.Path)
	t.Execute(buffer, p)

	return string(buffer.Bytes())
}

func (p Project) ArchivePath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Version.Value)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) TemporaryPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.BasePath()[:len(p.BasePath())-1])
	buffer.WriteString(".milou/")

	return string(buffer.Bytes())
}

func (p Project) ExtractPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Repository)

	return string(buffer.Bytes())
}

func (p Project) ArchiveLocation() string {
	var buffer bytes.Buffer

	buffer.WriteString("https://s3.amazonaws.com/")
	buffer.WriteString(p.Identifier)
	buffer.WriteString("/")
	buffer.WriteString(p.Version.Value)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) Extract() error {
	err := os.MkdirAll(p.ExtractPath(), 0700)

	if err != nil {
		return err
	}

	r, err := zip.OpenReader(p.ArchivePath())
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		fpath := filepath.Join(p.ExtractPath(), f.Name)
		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, f.Mode())
		} else {
			var fdir string
			if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
				fdir = fpath[:lastIndex]
			}

			err = os.MkdirAll(fdir, f.Mode())
			if err != nil {
				log.Fatal(err)
				return err
			}
			f, err := os.OpenFile(
				fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			defer f.Close()

			_, err = io.Copy(f, rc)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (p Project) Download() {
	response, err := http.Get(p.ArchiveLocation())
	defer response.Body.Close()

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

	archive, err := os.Create(p.ArchivePath())
	defer archive.Close()

	_, err = io.Copy(archive, response.Body)

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

func (p Project) Place() {
	err := os.RemoveAll(p.BasePath())

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

	err = os.Rename(p.ExtractPath(), p.BasePath())

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

func (p Project) Prepare() {
	if _, err := os.Stat(p.TemporaryPath()); os.IsNotExist(err) {
		err = os.MkdirAll(p.TemporaryPath(), 0700)

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

func (p Project) CleanUp() {
	err := os.RemoveAll(p.TemporaryPath())

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

func (p Project) Deploy() {
	p.Prepare()
	p.Download()
	p.Extract()
	p.Place()
	p.CleanUp()
}

func DeployAll() {
	for _, project := range list {
		project.Deploy()
	}
}

func Process(n notifications.Notification) {
	for _, project := range list {
		if project.Repository != n.Repository {
			continue
		}

		if project.Owner != n.Owner {
			continue
		}

		if project.Version.Type == "commit" {
			if project.Version.Value == n.Commit {
				project.Deploy()
				continue
			}
		}

		if project.Version.Type == "branch" {
			if project.Version.Regex {
				match, _ := regexp.MatchString(project.Version.Value, n.Branch)

				if match {
					project.Deploy()
				}
			} else if project.Version.Value == n.Branch {
				project.Deploy()
			}
		}
	}
}
<|endoftext|>


<commit_before>package bot

import (
	"bytes"
	"encoding/base32"
	"fmt"
	"os"
	"strings"
	"time"

	otp "github.com/dgryski/dgoogauth"
	"github.com/ghodss/yaml"
)

// if help is more than tooLong lines long, send a private message
const tooLong = 14

// Size of QR code
const qrsize = 400

// If this list doesn't match what's registered below,
// you're gonna have a bad time.
var builtIns = []string{
	"builtInhelp",
	"builtInadmin",
	"builtIndump",
	"builtInlaunchcodes",
}

func init() {
	RegisterPlugin("builtIndump", PluginHandler{DefaultConfig: dumpConfig, Handler: dump})
	RegisterPlugin("builtInhelp", PluginHandler{DefaultConfig: helpConfig, Handler: help})
	RegisterPlugin("builtInadmin", PluginHandler{DefaultConfig: adminConfig, Handler: admin})
	RegisterPlugin("builtInlaunchcodes", PluginHandler{DefaultConfig: launchCodesConfig, Handler: launchCode})
}

/* builtin plugins, like help */

func launchCode(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	var userOTP otp.OTPConfig
	otpKey := "bot:OTP:" + bot.User
	updated := false
	lock, exists, ret := checkoutDatum(otpKey, &userOTP, true)
	if ret != Ok {
		bot.Say("Yikes - something went wrong with my brain, have somebody check my log")
		return
	}
	defer func() {
		if updated {
			ret := updateDatum(otpKey, lock, &userOTP)
			if ret != Ok {
				Log(Error, "Couldn't save OTP config")
				bot.Reply("Good grief. I'm having trouble remembering your launch codes - have somebody check my log")
			}
		} else {
			// Well-behaved plugins will always do a CheckinDatum when the datum hasn't been updated,
			// in case there's another thread waiting.
			checkinDatum(otpKey, lock)
		}
	}()
	switch command {
	case "send":
		if exists {
			bot.Reply("I've already sent you the launch codes, contact an administrator if you're having problems")
			return
		}
		otpb := make([]byte, 10)
		random.Read(otpb)
		userOTP.Secret = base32.StdEncoding.EncodeToString(otpb)
		userOTP.WindowSize = 2
		userOTP.DisallowReuse = []int{}
		var codeMail bytes.Buffer
		fmt.Fprintf(&codeMail, "For your authenticator:\n%s\n", userOTP.Secret)
		if ret := bot.Email("Your launch codes - if you print this email, please chew it up and swallow it", &codeMail); ret != Ok {
			bot.Reply("There was a problem sending your launch codes, contact an administrator")
			return
		}
		updated = true
		bot.Reply("I've emailed your launch codes - please delete it promptly")
	case "check":
		if !exists {
			bot.Reply("It doesn't appear you've been issued any launch codes, try 'send launch codes'")
			return
		}
		valid, err := userOTP.Authenticate(args[0])
		if err != nil {
			Log(Error, fmt.Errorf("Problem authenticating launch code: %v", err))
			bot.Reply("There was an error authenticating your launch code, have an adminstrator check the log")
			return
		}
		// Whether valid or not, the otp lib may update the struct
		updated = true
		if valid {
			bot.Reply("The launch code was valid")
		} else {
			bot.Reply("You supplied an invalid launch code, and I've contacted POTUS and the NSA")
		}
	}
}

func help(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if command == "help" {
		b.lock.RLock()
		defer b.lock.RUnlock()

		var term, helpOutput string
		botSub := `(bot)`
		hasTerm := false
		helpLines := 0
		if len(args) == 1 && len(args[0]) > 0 {
			hasTerm = true
			term = args[0]
			if term == "help" {
				Log(Trace, "Help requested for help, returning")
				return
			}
			Log(Trace, "Help requested for term", term)
		}

		for _, plugin := range plugins {
			Log(Trace, fmt.Sprintf("Checking help for plugin %s (term: %s)", plugin.name, term))
			if !hasTerm { // if you ask for help without a term, you just get help for whatever commands are available to you
				if messageAppliesToPlugin(bot.User, bot.Channel, plugin) {
					for _, phelp := range plugin.Help {
						for _, helptext := range phelp.Helptext {
							if len(phelp.Keywords) > 0 && phelp.Keywords[0] == "*" {
								// * signifies help that should be prepended
								helpOutput = strings.Replace(helptext, botSub, b.name, -1) + string('\n') + helpOutput
							} else {
								helpOutput += strings.Replace(helptext, botSub, b.name, -1) + string('\n')
							}
							helpLines++
						}
					}
				}
			} else { // when there's a search term, give all help for that term, but add (channels: xxx) at the end
				for _, phelp := range plugin.Help {
					for _, keyword := range phelp.Keywords {
						if term == keyword {
							chantext := ""
							if plugin.DirectOnly {
								// Look: the right paren gets added below
								chantext = " (direct message only"
							} else {
								for _, pchan := range plugin.Channels {
									if len(chantext) == 0 {
										chantext += " (channels: " + pchan
									} else {
										chantext += ", " + pchan
									}
								}
							}
							if len(chantext) != 0 {
								chantext += ")"
							}
							for _, helptext := range phelp.Helptext {
								helpOutput += strings.Replace(helptext, botSub, b.name, -1) + chantext + string('\n')
								helpLines++
							}
						}
					}
				}
			}
		}
		switch {
		case helpLines == 0:
			bot.Say("Sorry, bub - I got nothin' for ya'")
		case helpLines > tooLong:
			if len(bot.Channel) > 0 {
				bot.Reply("(the help output was pretty long, so I sent you a private message)")
				if hasTerm {
					helpOutput = "Help for term: " + term + "\n" + helpOutput
				} else {
					helpOutput = "Help for commands available in channel: " + bot.Channel + "\n" + helpOutput
				}
			}
			bot.SendUserMessage(bot.User, strings.TrimRight(helpOutput, "\n"))
		default:
			bot.Say(strings.TrimRight(helpOutput, "\n"))
		}
	}
}

func dump(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	b.lock.RLock()
	defer b.lock.RUnlock()
	switch command {
	case "robotdefault":
		bot.Fixed().Say(fmt.Sprintf("Here's my default configuration:\n%s", defaultConfig))
	case "robot":
		c, _ := yaml.Marshal(config)
		bot.Fixed().Say(fmt.Sprintf("Here's how I've been configured, irrespective of interactive changes:\n%s", c))
	case "plugdefault":
		if plug, ok := pluginHandlers[args[0]]; ok {
			bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], plug.DefaultConfig))
		} else { // look for an external plugin
			found := false
			for _, plugin := range plugins {
				if args[0] == plugin.name && plugin.pluginType == plugExternal {
					found = true
					if cfg, err := getExtDefCfg(plugin); err == nil {
						bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], *cfg))
					} else {
						bot.Say("I had a problem looking that up - somebody should check my logs")
					}
				}
			}
			if !found {
				bot.Say("Didn't find a plugin named " + args[0])
			}
		}
	case "plugin":
		found := false
		for _, plugin := range plugins {
			if args[0] == plugin.name {
				found = true
				c, _ := yaml.Marshal(plugin)
				bot.Fixed().Say(fmt.Sprintf("%s", c))
			}
		}
		if !found {
			bot.Say("Didn't find a plugin named " + args[0])
		}
	case "list":
		plist := make([]string, 0, len(plugins))
		for _, plugin := range plugins {
			plist = append(plist, plugin.name)
		}
		bot.Say(fmt.Sprintf("Here are the plugins I have configured:\n%s", strings.Join(plist, ", ")))
	}
}

var byebye = []string{
	"Sayonara!",
	"Adios",
	"Hasta la vista!",
	"Later gator!",
}

func admin(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if !bot.CheckAdmin() {
		bot.Reply("Sorry, only an admin user can request that")
		return
	}
	switch command {
	case "reload":
		err := loadConfig()
		if err != nil {
			bot.Reply("Error encountered during reload, check the logs")
			Log(Error, fmt.Errorf("Reloading configuration, requested by %s: %v", bot.User, err))
			return
		}
		bot.Reply("Configuration reloaded successfully")
		Log(Info, "Configuration successfully reloaded by a request from:", bot.User)
	case "quit":
		// Get all important locks to make sure nothing is being changed - then exit
		botLock.Lock()
		b.lock.Lock()
		dataLock.Lock()
		bot.Say(bot.RandomString(byebye))
		Log(Info, "Exiting on administrator command")
		// How long does it _actually_ take for the message to go out?
		time.Sleep(time.Second)
		os.Exit(0)
	}
}
<commit_msg>Improve help output<commit_after>package bot

import (
	"bytes"
	"encoding/base32"
	"fmt"
	"os"
	"strings"
	"time"

	otp "github.com/dgryski/dgoogauth"
	"github.com/ghodss/yaml"
)

// if help is more than tooLong lines long, send a private message
const tooLong = 14

// Size of QR code
const qrsize = 400

// If this list doesn't match what's registered below,
// you're gonna have a bad time.
var builtIns = []string{
	"builtInhelp",
	"builtInadmin",
	"builtIndump",
	"builtInlaunchcodes",
}

func init() {
	RegisterPlugin("builtIndump", PluginHandler{DefaultConfig: dumpConfig, Handler: dump})
	RegisterPlugin("builtInhelp", PluginHandler{DefaultConfig: helpConfig, Handler: help})
	RegisterPlugin("builtInadmin", PluginHandler{DefaultConfig: adminConfig, Handler: admin})
	RegisterPlugin("builtInlaunchcodes", PluginHandler{DefaultConfig: launchCodesConfig, Handler: launchCode})
}

/* builtin plugins, like help */

func launchCode(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	var userOTP otp.OTPConfig
	otpKey := "bot:OTP:" + bot.User
	updated := false
	lock, exists, ret := checkoutDatum(otpKey, &userOTP, true)
	if ret != Ok {
		bot.Say("Yikes - something went wrong with my brain, have somebody check my log")
		return
	}
	defer func() {
		if updated {
			ret := updateDatum(otpKey, lock, &userOTP)
			if ret != Ok {
				Log(Error, "Couldn't save OTP config")
				bot.Reply("Good grief. I'm having trouble remembering your launch codes - have somebody check my log")
			}
		} else {
			// Well-behaved plugins will always do a CheckinDatum when the datum hasn't been updated,
			// in case there's another thread waiting.
			checkinDatum(otpKey, lock)
		}
	}()
	switch command {
	case "send":
		if exists {
			bot.Reply("I've already sent you the launch codes, contact an administrator if you're having problems")
			return
		}
		otpb := make([]byte, 10)
		random.Read(otpb)
		userOTP.Secret = base32.StdEncoding.EncodeToString(otpb)
		userOTP.WindowSize = 2
		userOTP.DisallowReuse = []int{}
		var codeMail bytes.Buffer
		fmt.Fprintf(&codeMail, "For your authenticator:\n%s\n", userOTP.Secret)
		if ret := bot.Email("Your launch codes - if you print this email, please chew it up and swallow it", &codeMail); ret != Ok {
			bot.Reply("There was a problem sending your launch codes, contact an administrator")
			return
		}
		updated = true
		bot.Reply("I've emailed your launch codes - please delete it promptly")
	case "check":
		if !exists {
			bot.Reply("It doesn't appear you've been issued any launch codes, try 'send launch codes'")
			return
		}
		valid, err := userOTP.Authenticate(args[0])
		if err != nil {
			Log(Error, fmt.Errorf("Problem authenticating launch code: %v", err))
			bot.Reply("There was an error authenticating your launch code, have an adminstrator check the log")
			return
		}
		// Whether valid or not, the otp lib may update the struct
		updated = true
		if valid {
			bot.Reply("The launch code was valid")
		} else {
			bot.Reply("You supplied an invalid launch code, and I've contacted POTUS and the NSA")
		}
	}
}

func help(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if command == "help" {
		b.lock.RLock()
		defer b.lock.RUnlock()

		var term, helpOutput string
		botSub := `(bot)`
		hasTerm := false
		helpLines := 0
		if len(args) == 1 && len(args[0]) > 0 {
			hasTerm = true
			term = args[0]
			if term == "help" {
				Log(Trace, "Help requested for help, returning")
				return
			}
			Log(Trace, "Help requested for term", term)
		}

		for _, plugin := range plugins {
			Log(Trace, fmt.Sprintf("Checking help for plugin %s (term: %s)", plugin.name, term))
			if !hasTerm { // if you ask for help without a term, you just get help for whatever commands are available to you
				if messageAppliesToPlugin(bot.User, bot.Channel, plugin) {
					for _, phelp := range plugin.Help {
						for _, helptext := range phelp.Helptext {
							if len(phelp.Keywords) > 0 && phelp.Keywords[0] == "*" {
								// * signifies help that should be prepended
								helpOutput = strings.Replace(helptext, botSub, b.name, -1) + string('\n') + helpOutput
							} else {
								helpOutput += strings.Replace(helptext, botSub, b.name, -1) + string('\n')
							}
							helpLines++
						}
					}
				}
			} else { // when there's a search term, give all help for that term, but add (channels: xxx) at the end
				for _, phelp := range plugin.Help {
					for _, keyword := range phelp.Keywords {
						if term == keyword {
							chantext := ""
							if plugin.DirectOnly {
								// Look: the right paren gets added below
								chantext = " (direct message only"
							} else {
								for _, pchan := range plugin.Channels {
									if len(chantext) == 0 {
										chantext += " (channels: " + pchan
									} else {
										chantext += ", " + pchan
									}
								}
							}
							if len(chantext) != 0 {
								chantext += ")"
							}
							for _, helptext := range phelp.Helptext {
								helpOutput += strings.Replace(helptext, botSub, b.name, -1) + chantext + string('\n')
								helpLines++
							}
						}
					}
				}
			}
		}
		if hasTerm {
			helpOutput = "Command(s) matching keyword: " + term + "\n" + helpOutput
		}
		switch {
		case helpLines == 0:
			bot.Say("Sorry, bub - I got nothin' for ya'")
		case helpLines > tooLong:
			if len(bot.Channel) > 0 {
				bot.Reply("(the help output was pretty long, so I sent you a private message)")
				if !hasTerm {
					helpOutput = "Command(s) available in channel: " + bot.Channel + "\n" + helpOutput
				}
			}
			bot.SendUserMessage(bot.User, strings.TrimRight(helpOutput, "\n"))
		default:
			bot.Say(strings.TrimRight(helpOutput, "\n"))
		}
	}
}

func dump(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	b.lock.RLock()
	defer b.lock.RUnlock()
	switch command {
	case "robotdefault":
		bot.Fixed().Say(fmt.Sprintf("Here's my default configuration:\n%s", defaultConfig))
	case "robot":
		c, _ := yaml.Marshal(config)
		bot.Fixed().Say(fmt.Sprintf("Here's how I've been configured, irrespective of interactive changes:\n%s", c))
	case "plugdefault":
		if plug, ok := pluginHandlers[args[0]]; ok {
			bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], plug.DefaultConfig))
		} else { // look for an external plugin
			found := false
			for _, plugin := range plugins {
				if args[0] == plugin.name && plugin.pluginType == plugExternal {
					found = true
					if cfg, err := getExtDefCfg(plugin); err == nil {
						bot.Fixed().Say(fmt.Sprintf("Here's the default configuration for \"%s\":\n%s", args[0], *cfg))
					} else {
						bot.Say("I had a problem looking that up - somebody should check my logs")
					}
				}
			}
			if !found {
				bot.Say("Didn't find a plugin named " + args[0])
			}
		}
	case "plugin":
		found := false
		for _, plugin := range plugins {
			if args[0] == plugin.name {
				found = true
				c, _ := yaml.Marshal(plugin)
				bot.Fixed().Say(fmt.Sprintf("%s", c))
			}
		}
		if !found {
			bot.Say("Didn't find a plugin named " + args[0])
		}
	case "list":
		plist := make([]string, 0, len(plugins))
		for _, plugin := range plugins {
			plist = append(plist, plugin.name)
		}
		bot.Say(fmt.Sprintf("Here are the plugins I have configured:\n%s", strings.Join(plist, ", ")))
	}
}

var byebye = []string{
	"Sayonara!",
	"Adios",
	"Hasta la vista!",
	"Later gator!",
}

func admin(bot *Robot, command string, args ...string) {
	if command == "init" {
		return // ignore init
	}
	if !bot.CheckAdmin() {
		bot.Reply("Sorry, only an admin user can request that")
		return
	}
	switch command {
	case "reload":
		err := loadConfig()
		if err != nil {
			bot.Reply("Error encountered during reload, check the logs")
			Log(Error, fmt.Errorf("Reloading configuration, requested by %s: %v", bot.User, err))
			return
		}
		bot.Reply("Configuration reloaded successfully")
		Log(Info, "Configuration successfully reloaded by a request from:", bot.User)
	case "quit":
		// Get all important locks to make sure nothing is being changed - then exit
		botLock.Lock()
		b.lock.Lock()
		dataLock.Lock()
		bot.Say(bot.RandomString(byebye))
		Log(Info, "Exiting on administrator command")
		// How long does it _actually_ take for the message to go out?
		time.Sleep(time.Second)
		os.Exit(0)
	}
}
<|endoftext|>


<commit_before>package bot

import (
	"fmt"
	"regexp"
	"strings"
	"sync"

	"github.com/eclipse/paho.mqtt.golang"
	"github.com/garyburd/redigo/redis"
	"github.com/spf13/viper"
	"github.com/topfreegames/mqttbot/logger"
	"github.com/topfreegames/mqttbot/modules"
	"github.com/topfreegames/mqttbot/mqttclient"
	"github.com/topfreegames/mqttbot/plugins"
)

// PluginMapping defines the plugin to listen to given patterns
type PluginMapping struct {
	Plugin         string
	MessagePattern string
}

// Subscription defines the plugin mappings to a given topic
type Subscription struct {
	Topic          string
	Qos            int
	PluginMappings []*PluginMapping
}

// MQTTBot defines the bot, it contains plugins, subscriptions and a client
type MQTTBot struct {
	Plugins       *plugins.Plugins
	Subscriptions []*Subscription
	Client        *mqttclient.MQTTClient
	Config        *viper.Viper
}

var mqttBot *MQTTBot
var once sync.Once

var h mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
	for _, subscription := range mqttBot.Subscriptions {
		if RouteIncludesTopic(strings.Split(subscription.Topic, "/"), strings.Split(msg.Topic(), "/")) {
			for _, pluginMapping := range subscription.PluginMappings {
				match, _ := regexp.Match(pluginMapping.MessagePattern, msg.Payload())
				if match {
					mqttBot.Plugins.ExecutePlugin(string(msg.Payload()[:]), msg.Topic(), pluginMapping.Plugin)
				}
			}
		}
	}
}

//ResetMQTTBot clears once and mqttbot instance
func ResetMQTTBot() {
	once = sync.Once{}
	mqttBot = nil
}

// GetMQTTBot returns a initialized mqtt bot
func GetMQTTBot() *MQTTBot {
	once.Do(func() {
		addCredentialsToRedis()
		mqttBot = &MQTTBot{}
		mqttBot.Client = mqttclient.GetMQTTClient(onClientConnectHandler)
		mqttBot.setupPlugins()
	})
	return mqttBot
}

func (b *MQTTBot) setupPlugins() {
	b.Plugins = plugins.GetPlugins()
	b.Plugins.SetupPlugins()
}

var onClientConnectHandler = func(client mqtt.Client) {
	mqttBot.StartBot()
}

// StartBot starts the bot, it subscribes the bot to the topics defined in the
// configuration file
func (b *MQTTBot) StartBot() {
	subscriptions := viper.Get("mqttserver.subscriptionRequests").([]interface{})
	client := b.Client.MQTTClient
	fmt.Println(12)
	b.Subscriptions = []*Subscription{}
	for _, s := range subscriptions {
		sMap := s.(map[interface{}]interface{})
		qos := sMap[string("qos")].(int)
		topic := sMap[string("topic")].(string)
		pluginMapping := sMap[string("plugins")].([]interface{})
		subscriptionNow := &Subscription{
			Topic:          topic,
			Qos:            qos,
			PluginMappings: []*PluginMapping{},
		}
		for _, p := range pluginMapping {
			pMap := p.(map[interface{}]interface{})
			subscriptionNow.PluginMappings = append(subscriptionNow.PluginMappings, &PluginMapping{
				Plugin:         pMap[string("plugin")].(string),
				MessagePattern: pMap[string("messagePattern")].(string),
			})
		}
		if token := client.Subscribe(topic, uint8(qos), h); token.Wait() && token.Error() != nil {
			logger.Logger.Fatal(token.Error())
		}
		logger.Logger.Debug(fmt.Sprintf("Subscribed to %s", topic))
		b.Subscriptions = append(b.Subscriptions, subscriptionNow)
	}
}

func addCredentialsToRedis() {
	viper.SetDefault("redis.host", "localhost")
	viper.SetDefault("redis.port", 6379)
	viper.SetDefault("redis.password", "")
	viper.SetDefault("redis.maxPoolSize", 10)
	user := viper.GetString("mqttserver.user")
	pass := viper.GetString("mqttserver.pass")
	hash := modules.GenHash(pass)
	redisHost := viper.GetString("redis.host")
	redisPort := viper.GetInt("redis.port")
	redisPass := viper.GetString("redis.password")
	logger.Logger.Info(fmt.Sprintf("Connecting to redis at %s:%d", redisHost, redisPort))
	conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", redisHost, redisPort),
		redis.DialPassword(redisPass))
	if err != nil {
		conn, err = redis.Dial("tcp", fmt.Sprintf("%s:%d", redisHost, redisPort))
		if err != nil {
			logger.Logger.Fatal(fmt.Sprintf("Error connecting to Redis: %v", err))
			return
		}
	}
	defer conn.Close()
	if _, err = conn.Do("SET", user, hash); err != nil {
		logger.Logger.Error(fmt.Sprintf("Error adding pass to redis: %v", err))
	}
}
<commit_msg>Ensuring unsubscribe, before a new subscribbe<commit_after>package bot

import (
	"fmt"
	"regexp"
	"strings"
	"sync"

	"github.com/eclipse/paho.mqtt.golang"
	"github.com/garyburd/redigo/redis"
	"github.com/spf13/viper"
	"github.com/topfreegames/mqttbot/logger"
	"github.com/topfreegames/mqttbot/modules"
	"github.com/topfreegames/mqttbot/mqttclient"
	"github.com/topfreegames/mqttbot/plugins"
)

// PluginMapping defines the plugin to listen to given patterns
type PluginMapping struct {
	Plugin         string
	MessagePattern string
}

// Subscription defines the plugin mappings to a given topic
type Subscription struct {
	Topic          string
	Qos            int
	PluginMappings []*PluginMapping
}

// MQTTBot defines the bot, it contains plugins, subscriptions and a client
type MQTTBot struct {
	Plugins       *plugins.Plugins
	Subscriptions []*Subscription
	Client        *mqttclient.MQTTClient
	Config        *viper.Viper
}

var mqttBot *MQTTBot
var once sync.Once

var h mqtt.MessageHandler = func(client mqtt.Client, msg mqtt.Message) {
	for _, subscription := range mqttBot.Subscriptions {
		if RouteIncludesTopic(strings.Split(subscription.Topic, "/"), strings.Split(msg.Topic(), "/")) {
			for _, pluginMapping := range subscription.PluginMappings {
				match, _ := regexp.Match(pluginMapping.MessagePattern, msg.Payload())
				if match {
					mqttBot.Plugins.ExecutePlugin(string(msg.Payload()[:]), msg.Topic(), pluginMapping.Plugin)
				}
			}
		}
	}
}

//ResetMQTTBot clears once and mqttbot instance
func ResetMQTTBot() {
	once = sync.Once{}
	mqttBot = nil
}

// GetMQTTBot returns a initialized mqtt bot
func GetMQTTBot() *MQTTBot {
	once.Do(func() {
		addCredentialsToRedis()
		mqttBot = &MQTTBot{}
		mqttBot.Client = mqttclient.GetMQTTClient(onClientConnectHandler)
		mqttBot.setupPlugins()
	})
	return mqttBot
}

func (b *MQTTBot) setupPlugins() {
	b.Plugins = plugins.GetPlugins()
	b.Plugins.SetupPlugins()
}

var onClientConnectHandler = func(client mqtt.Client) {
	mqttBot.StartBot()
}

// StartBot starts the bot, it subscribes the bot to the topics defined in the
// configuration file
func (b *MQTTBot) StartBot() {
	subscriptions := viper.Get("mqttserver.subscriptionRequests").([]interface{})
	client := b.Client.MQTTClient
	fmt.Println(12)
	b.Subscriptions = []*Subscription{}
	for _, s := range subscriptions {
		sMap := s.(map[interface{}]interface{})
		qos := sMap[string("qos")].(int)
		topic := sMap[string("topic")].(string)
		pluginMapping := sMap[string("plugins")].([]interface{})
		subscriptionNow := &Subscription{
			Topic:          topic,
			Qos:            qos,
			PluginMappings: []*PluginMapping{},
		}
		for _, p := range pluginMapping {
			pMap := p.(map[interface{}]interface{})
			subscriptionNow.PluginMappings = append(subscriptionNow.PluginMappings, &PluginMapping{
				Plugin:         pMap[string("plugin")].(string),
				MessagePattern: pMap[string("messagePattern")].(string),
			})
		}
		client.Unsubscribe(topic)
		if token := client.Subscribe(topic, uint8(qos), h); token.Wait() && token.Error() != nil {
			logger.Logger.Fatal(token.Error())
		}
		logger.Logger.Debug(fmt.Sprintf("Subscribed to %s", topic))
		b.Subscriptions = append(b.Subscriptions, subscriptionNow)
	}
}

func addCredentialsToRedis() {
	viper.SetDefault("redis.host", "localhost")
	viper.SetDefault("redis.port", 6379)
	viper.SetDefault("redis.password", "")
	viper.SetDefault("redis.maxPoolSize", 10)
	user := viper.GetString("mqttserver.user")
	pass := viper.GetString("mqttserver.pass")
	hash := modules.GenHash(pass)
	redisHost := viper.GetString("redis.host")
	redisPort := viper.GetInt("redis.port")
	redisPass := viper.GetString("redis.password")
	logger.Logger.Info(fmt.Sprintf("Connecting to redis at %s:%d", redisHost, redisPort))
	conn, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", redisHost, redisPort),
		redis.DialPassword(redisPass))
	if err != nil {
		conn, err = redis.Dial("tcp", fmt.Sprintf("%s:%d", redisHost, redisPort))
		if err != nil {
			logger.Logger.Fatal(fmt.Sprintf("Error connecting to Redis: %v", err))
			return
		}
	}
	defer conn.Close()
	if _, err = conn.Do("SET", user, hash); err != nil {
		logger.Logger.Error(fmt.Sprintf("Error adding pass to redis: %v", err))
	}
}
<|endoftext|>


<commit_before>package prompt

import (
	"fmt"
	"os/exec"
	"strings"
)

func OSAScriptMfaPrompt(mfaSerial string) (string, error) {
	cmd := exec.Command("osascript", "-e", fmt.Sprintf(`
		display dialog "%s" default answer "" buttons {"OK", "Cancel"} default button 1
        text returned of the result
        return result`,
		mfaPromptMessage(mfaSerial)))

	out, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(string(out)), nil
}

func init() {
	Methods["osascript"] = OSAScriptMfaPrompt
}
<commit_msg>Prevent osascript prompt code injection<commit_after>package prompt

import (
	"fmt"
	"os/exec"
	"strings"
)

func OSAScriptMfaPrompt(mfaSerial string) (string, error) {
	cmd := exec.Command("osascript", "-e", fmt.Sprintf(`
		display dialog %q default answer "" buttons {"OK", "Cancel"} default button 1
        text returned of the result
        return result`,
		mfaPromptMessage(mfaSerial)))

	out, err := cmd.Output()
	if err != nil {
		return "", err
	}

	return strings.TrimSpace(string(out)), nil
}

func init() {
	Methods["osascript"] = OSAScriptMfaPrompt
}
<|endoftext|>


<commit_before>package sphere

import redis "gopkg.in/redis.v3"

var (
	roption = redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	}
	pubclient = redis.NewClient(&roption)
	subclient = redis.NewClient(&roption)
)

// NewRedisBroker creates a new instance of RedisBroker
func NewRedisBroker() *RedisBroker {
	return &RedisBroker{
		make(map[*Channel]*redis.PubSub),
		ExtendBroker(),
	}
}

// RedisBroker is a broker adapter built on Redis client
type RedisBroker struct {
	store map[*Channel]*redis.PubSub
	*Broker
}

// OnSubscribe when websocket subscribes to a channel
func (broker *RedisBroker) OnSubscribe(channel *Channel) error {
	c := make(chan error)
	go func() {
		pubsub, err := subclient.Subscribe(channel.name)
		if err == nil {
			broker.store[channel] = pubsub
		}
		c <- err
	}()
	return <-c
}

// OnUnsubscribe when websocket unsubscribes from a channel
func (broker *RedisBroker) OnUnsubscribe(channel *Channel) error {
	c := make(chan error)
	go func() {
		if pubsub := broker.store[channel]; pubsub != nil {
			// close pubsub handler
			c <- pubsub.Close()
			// remove pubsub from store
			defer delete(broker.store, channel)
		} else {
			c <- nil
		}
	}()
	return <-c
}

// OnPublish when websocket publishes data to a particular channel from the current broker
func (broker *RedisBroker) OnPublish(channel *Channel, data interface{}) error {
	return nil
}

// OnMessage when websocket receive data from the broker subscriber
func (broker *RedisBroker) OnMessage(channel *Channel, data interface{}) error {
	return nil
}
<commit_msg>only create pubsub when it does not existed<commit_after>package sphere

import redis "gopkg.in/redis.v3"

var (
	roption = redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	}
	pubclient = redis.NewClient(&roption)
	subclient = redis.NewClient(&roption)
)

// NewRedisBroker creates a new instance of RedisBroker
func NewRedisBroker() *RedisBroker {
	return &RedisBroker{
		make(map[*Channel]*redis.PubSub),
		ExtendBroker(),
	}
}

// RedisBroker is a broker adapter built on Redis client
type RedisBroker struct {
	store map[*Channel]*redis.PubSub
	*Broker
}

// OnSubscribe when websocket subscribes to a channel
func (broker *RedisBroker) OnSubscribe(channel *Channel) error {
	c := make(chan error)
	go func() {
		// return if pubsub is already existed
		if pubsub := broker.store[channel]; pubsub != nil {
			c <- nil
			return
		}
		// creates subscribe pubsub
		pubsub, err := subclient.Subscribe(channel.name)
		if err == nil {
			broker.store[channel] = pubsub
		}
		// close pubsub when process is done
		defer broker.OnUnsubscribe(channel)
		for {
			_, err := pubsub.ReceiveMessage()
			if err != nil {
				c <- err
				return
			}
		}
	}()
	return <-c
}

// OnUnsubscribe when websocket unsubscribes from a channel
func (broker *RedisBroker) OnUnsubscribe(channel *Channel) error {
	c := make(chan error)
	go func() {
		if pubsub := broker.store[channel]; pubsub != nil {
			// close pubsub handler
			c <- pubsub.Close()
			// remove pubsub from store
			defer delete(broker.store, channel)
		} else {
			c <- nil
		}
	}()
	return <-c
}

// OnPublish when websocket publishes data to a particular channel from the current broker
func (broker *RedisBroker) OnPublish(channel *Channel, data interface{}) error {
	return nil
}

// OnMessage when websocket receive data from the broker subscriber
func (broker *RedisBroker) OnMessage(channel *Channel, data interface{}) error {
	return nil
}
<|endoftext|>


<commit_before>package browserspeak

import (
	"errors"
	"fmt"
	"net/http"
	"strings"
)

const (
	Version     = 0.8
	noAttribute = "NIL"
)

type Tag struct {
	name        string
	style       map[string]string
	content     string
	lastContent string
	xmlContent  string
	attrs       map[string]string
	nextSibling *Tag // siblings
	firstChild  *Tag // first child
}

type Page struct {
	title  string
	root   *Tag
	cursor *Tag
}

// Create a new XML/HTML page, with a root tag
// If rootTagName contains "<" or ">", it can be used for preceding declarations,
// like <!DOCTYPE html> or <?xml version=\"1.0\"?>.
// Returns a pointer to a Page.
func NewPage(title, rootTagName string) *Page {
	var page Page
	page.title = title
	rootTag := NewTag(rootTagName)
	page.root = rootTag
	return &page
}

// Create a new tag based on a given name.
// name is what will appear right after the "<" in the tag output
func NewTag(name string) *Tag {
	var tag Tag
	tag.name = name
	tag.style = make(map[string]string)
	tag.attrs = make(map[string]string)
	tag.nextSibling = nil
	tag.firstChild = nil
	tag.content = ""
	tag.lastContent = ""
	return &tag
}

// Add a new tag to another tag. This will place it one step
// lower in the hierarchy of tags. You can for example add
// a body tag to an html tag.
func (tag *Tag) AddNewTag(name string) *Tag {
	child := NewTag(name)
	tag.AddChild(child)
	return child
}

// Add a tag to another tag
func (tag *Tag) AddTag(child *Tag) {
	tag.AddChild(child)
}

// Add CSS style to a tag, for instance
// "background-color" and "red"
func (tag *Tag) AddStyle(styleName, styleValue string) {
	tag.style[styleName] = styleValue
}

// Add an attribute to a tag, for instance
// "size" and "20"
func (tag *Tag) AddAttrib(attrName, attrValue string) {
	tag.attrs[attrName] = attrValue
}

// Add an attribute without a value
func (tag *Tag) AddSingularAttrib(attrName string) {
	tag.attrs[attrName] = noAttribute
}

// Generate the CSS text for a given tag
// The generated string can be used directly in a CSS file
func (tag *Tag) GetCSS() string {
	if len(tag.style) == 0 {
		return ""
	}

	ret := ""

	// TODO: Support for "class" as well as "id"

	// If there is an id="name" defined, use that id instead of the tag name

	if value, found := tag.attrs["id"]; found {
		ret = "#" + value
	} else if value, found := tag.attrs["class"]; found {
		ret = "." + value
	} else {
		ret = tag.name
	}
	ret += " {\n"

	// Attributes may appear in any order
	for key, value := range tag.style {
		ret += "  " + key + ": " + value + ";\n"
	}

	ret += "}\n\n"
	return ret
}

// Get a string that represents all the attribute keys and values
// of a tag. This can be used when generating HTML, for example.
func (tag *Tag) GetAttrString() string {
	ret := ""
	for key, value := range tag.attrs {
		if value == noAttribute {
			ret += key + " "
		} else {
			ret += key + "=\"" + value + "\"" + " "
		}
	}
	if len(ret) > 0 {
		ret = ret[:len(ret)-1]
	}
	return ret
}

// Get spaces for indenting based on a given level
func getSpaces(level int) string {
	spacing := ""
	for i := 1; i < level; i++ {
		spacing += "  "
	}
	return spacing
}

// Generate a string for a tag, non-recursively
// indent is if the output should be indented or nto
// level is how many levels deep the output should be indented.
func (tag *Tag) getFlatXML(indent bool, level int) string {
	newLine := ""
	if indent {
		newLine = "\n"
	}
	// For the root tag
	if (len(tag.name) > 0) && (tag.name[0] == '<') {
		return tag.name + newLine + tag.content + tag.xmlContent + tag.lastContent
	}
	// For indenting
	spacing := ""
	if indent {
		spacing = getSpaces(level)
	}
	// Generate the XML based on the tag
	attrs := tag.GetAttrString()
	ret := spacing + "<" + tag.name
	if len(attrs) > 0 {
		ret += " " + attrs
	}
	if (len(tag.content) == 0) && (len(tag.xmlContent) == 0) && (len(tag.lastContent) == 0) {
		ret += " />"
	} else {
		if len(tag.xmlContent) > 0 {
			if tag.xmlContent[0] != ' ' {
				ret += ">" + newLine + spacing + tag.xmlContent + newLine + spacing + "</" + tag.name + ">"
			} else {
				ret += ">" + newLine + tag.xmlContent + spacing + "</" + tag.name + ">"
			}
		} else {
			ret += ">" + tag.content + tag.lastContent + "</" + tag.name + ">"
		}
	}
	return ret
}

// Get all the children for a given tag
// Returns a slice of pointers to tags
func (tag *Tag) GetChildren() []*Tag {
	var children []*Tag
	current := tag.firstChild
	for current != nil {
		children = append(children, current)
		current = current.nextSibling
	}
	return children
}

// Add a tag as a child to another tag
func (tag *Tag) AddChild(child *Tag) {
	if tag.firstChild == nil {
		tag.firstChild = child
		return
	}
	lastChild := tag.LastChild()
	child.nextSibling = nil
	lastChild.nextSibling = child
}

// Add content to a tag. This is what will appear
// between two tags, for example: <tag>content</tag>
// If the tag contains child tags, they will be added after this
func (tag *Tag) AddContent(content string) {
	tag.content += content
}

// Add content that will be added within a tag, but after
// the other content and any child tags
func (tag *Tag) AddLastContent(content string) {
	tag.lastContent += content
}

// Count how many children a tag has
// Returns an integer
func (tag *Tag) CountChildren() int {
	child := tag.firstChild
	if child == nil {
		return 0
	}
	count := 1
	if child.nextSibling == nil {
		return count
	}
	child = child.nextSibling
	for child != nil {
		count++
		child = child.nextSibling
	}
	return count
}

// Count the number of siblings a tag has
func (tag *Tag) CountSiblings() int {
	sib := tag.nextSibling
	if sib == nil {
		return 0
	}
	count := 1
	if sib.nextSibling == nil {
		return count
	}
	sib = sib.nextSibling
	for sib != nil {
		count++
		sib = sib.nextSibling
	}
	return count
}

// Find the last child of the children of a tag
func (tag *Tag) LastChild() *Tag {
	child := tag.firstChild
	for child.nextSibling != nil {
		child = child.nextSibling
	}
	return child
}

// Given the name of a tag, finds the first tag that matches
func (page *Page) GetTag(name string) (*Tag, error) {
	return page.root.GetTag(name)
}

// Find a tag by name, returns an error if not found
// Returns the first tag that matches
func (tag *Tag) GetTag(name string) (*Tag, error) {
	if strings.Index(tag.name, name) == 0 {
		return tag, nil
	}
	couldNotFindError := errors.New("Could not find tag: " + name)
	if tag.CountChildren() == 0 {
		// No children. Not found so far
		return nil, couldNotFindError
	}

	var child *Tag = tag.firstChild
	for child != nil {
		found, err := child.GetTag(name)
		if err == nil {
			return found, err
		}
		child = child.nextSibling
	}

	return nil, couldNotFindError
}

// Generate XML for a tag, recursively
// indent is if the output should be indented or not
// level is the indentation level
// Returns the generated XML as a string
func getXMLRecursively(cursor *Tag, indent bool, level int) string {

	newLine := ""
	if indent {
		newLine = "\n"
	}

	if cursor.CountChildren() == 0 {
		return cursor.getFlatXML(indent, level) + newLine
	}

	content := ""
	xmlContent := ""

	level++

	child := cursor.firstChild
	for child != nil {
		xmlContent = getXMLRecursively(child, indent, level)
		if len(xmlContent) > 0 {
			content += xmlContent
		}
		child = child.nextSibling
	}

	level--

	cursor.xmlContent = cursor.content + content + cursor.lastContent

	ret := cursor.getFlatXML(indent, level)
	if level > 0 {
		ret += newLine
	}
	return ret
}

// Generate CSS for a tag, recursively
// Returns the generated CSS as a string
// The output can go directly in a CSS file
func getCSSRecursively(cursor *Tag) string {
	if cursor.CountChildren() == 0 {
		return cursor.GetCSS()
	}

	style := ""
	cssContent := ""

	child := cursor.firstChild
	for child != nil {
		cssContent = getCSSRecursively(child)
		if len(cssContent) > 0 {
			style += cssContent
		}
		child = child.nextSibling
	}

	return cursor.GetCSS() + style
}

// Generate XML for a page
// The output can go directly in an XML file
func (page *Page) GetXML(indent bool) string {
	return getXMLRecursively(page.root, indent, 0)
}

// Generate CSS for a page
// The output can go directly in a CSS file
func (page *Page) GetCSS() string {
	return getCSSRecursively(page.root)
}

// Generate HTML for a page
func (page *Page) GetHTML() string {
	return page.GetXML(true)
}

// Show various information for a page, used for debugging
func (page *Page) prettyPrint() {
	root := *page.root
	fmt.Println("Page title:", page.title)
	fmt.Println("Page root tag name:", root.name)
	fmt.Println("Root tag children count:", root.CountChildren())
	fmt.Printf("HTML:\n%s\n", page.GetXML(true))
	fmt.Printf("CSS:\n%s\n", page.GetCSS())
}

// Add content to the body tag
// Returns the body tag and nil if successful
// Returns and an error if no body tag is found, else nil
func (page *Page) AddContent(content string) (*Tag, error) {
	body, err := page.root.GetTag("body")
	if err == nil {
		body.AddContent(content)
	}
	return body, err
}

// Get the string for the page
func (page *Page) String() string {
	return page.GetHTML()
}

// Publish the linked HTML and CSS for a Page
func (page *Page) Publish(mux *http.ServeMux, htmlurl, cssurl string) {
	page.LinkToCSS(cssurl)
	mux.HandleFunc(htmlurl, func(w http.ResponseWriter, req *http.Request) {
		w.Header().Add("Content-Type", "text/html")
		fmt.Fprintf(w, page.GetHTML())
	})
	mux.HandleFunc(cssurl, func(w http.ResponseWriter, req *http.Request) {
		w.Header().Add("Content-Type", "text/css")
		fmt.Fprintf(w, page.GetCSS())
	})
}
<commit_msg>Added simple caching<commit_after>package browserspeak

import (
	"errors"
	"fmt"
	"net/http"
	"strings"
)

const (
	Version     = 0.8
	noAttribute = "NIL"
)

type Tag struct {
	name        string
	style       map[string]string
	content     string
	lastContent string
	xmlContent  string
	attrs       map[string]string
	nextSibling *Tag // siblings
	firstChild  *Tag // first child
}

type Page struct {
	title  string
	root   *Tag
	cursor *Tag
}

// Create a new XML/HTML page, with a root tag
// If rootTagName contains "<" or ">", it can be used for preceding declarations,
// like <!DOCTYPE html> or <?xml version=\"1.0\"?>.
// Returns a pointer to a Page.
func NewPage(title, rootTagName string) *Page {
	var page Page
	page.title = title
	rootTag := NewTag(rootTagName)
	page.root = rootTag
	return &page
}

// Create a new tag based on a given name.
// name is what will appear right after the "<" in the tag output
func NewTag(name string) *Tag {
	var tag Tag
	tag.name = name
	tag.style = make(map[string]string)
	tag.attrs = make(map[string]string)
	tag.nextSibling = nil
	tag.firstChild = nil
	tag.content = ""
	tag.lastContent = ""
	return &tag
}

// Add a new tag to another tag. This will place it one step
// lower in the hierarchy of tags. You can for example add
// a body tag to an html tag.
func (tag *Tag) AddNewTag(name string) *Tag {
	child := NewTag(name)
	tag.AddChild(child)
	return child
}

// Add a tag to another tag
func (tag *Tag) AddTag(child *Tag) {
	tag.AddChild(child)
}

// Add CSS style to a tag, for instance
// "background-color" and "red"
func (tag *Tag) AddStyle(styleName, styleValue string) {
	tag.style[styleName] = styleValue
}

// Add an attribute to a tag, for instance
// "size" and "20"
func (tag *Tag) AddAttrib(attrName, attrValue string) {
	tag.attrs[attrName] = attrValue
}

// Add an attribute without a value
func (tag *Tag) AddSingularAttrib(attrName string) {
	tag.attrs[attrName] = noAttribute
}

// Generate the CSS text for a given tag
// The generated string can be used directly in a CSS file
func (tag *Tag) GetCSS() string {
	if len(tag.style) == 0 {
		return ""
	}

	ret := ""

	// TODO: Support for "class" as well as "id"

	// If there is an id="name" defined, use that id instead of the tag name

	if value, found := tag.attrs["id"]; found {
		ret = "#" + value
	} else if value, found := tag.attrs["class"]; found {
		ret = "." + value
	} else {
		ret = tag.name
	}
	ret += " {\n"

	// Attributes may appear in any order
	for key, value := range tag.style {
		ret += "  " + key + ": " + value + ";\n"
	}

	ret += "}\n\n"
	return ret
}

// Get a string that represents all the attribute keys and values
// of a tag. This can be used when generating HTML, for example.
func (tag *Tag) GetAttrString() string {
	ret := ""
	for key, value := range tag.attrs {
		if value == noAttribute {
			ret += key + " "
		} else {
			ret += key + "=\"" + value + "\"" + " "
		}
	}
	if len(ret) > 0 {
		ret = ret[:len(ret)-1]
	}
	return ret
}

// Get spaces for indenting based on a given level
func getSpaces(level int) string {
	spacing := ""
	for i := 1; i < level; i++ {
		spacing += "  "
	}
	return spacing
}

// Generate a string for a tag, non-recursively
// indent is if the output should be indented or nto
// level is how many levels deep the output should be indented.
func (tag *Tag) getFlatXML(indent bool, level int) string {
	newLine := ""
	if indent {
		newLine = "\n"
	}
	// For the root tag
	if (len(tag.name) > 0) && (tag.name[0] == '<') {
		return tag.name + newLine + tag.content + tag.xmlContent + tag.lastContent
	}
	// For indenting
	spacing := ""
	if indent {
		spacing = getSpaces(level)
	}
	// Generate the XML based on the tag
	attrs := tag.GetAttrString()
	ret := spacing + "<" + tag.name
	if len(attrs) > 0 {
		ret += " " + attrs
	}
	if (len(tag.content) == 0) && (len(tag.xmlContent) == 0) && (len(tag.lastContent) == 0) {
		ret += " />"
	} else {
		if len(tag.xmlContent) > 0 {
			if tag.xmlContent[0] != ' ' {
				ret += ">" + newLine + spacing + tag.xmlContent + newLine + spacing + "</" + tag.name + ">"
			} else {
				ret += ">" + newLine + tag.xmlContent + spacing + "</" + tag.name + ">"
			}
		} else {
			ret += ">" + tag.content + tag.lastContent + "</" + tag.name + ">"
		}
	}
	return ret
}

// Get all the children for a given tag
// Returns a slice of pointers to tags
func (tag *Tag) GetChildren() []*Tag {
	var children []*Tag
	current := tag.firstChild
	for current != nil {
		children = append(children, current)
		current = current.nextSibling
	}
	return children
}

// Add a tag as a child to another tag
func (tag *Tag) AddChild(child *Tag) {
	if tag.firstChild == nil {
		tag.firstChild = child
		return
	}
	lastChild := tag.LastChild()
	child.nextSibling = nil
	lastChild.nextSibling = child
}

// Add content to a tag. This is what will appear
// between two tags, for example: <tag>content</tag>
// If the tag contains child tags, they will be added after this
func (tag *Tag) AddContent(content string) {
	tag.content += content
}

// Add content that will be added within a tag, but after
// the other content and any child tags
func (tag *Tag) AddLastContent(content string) {
	tag.lastContent += content
}

// Count how many children a tag has
// Returns an integer
func (tag *Tag) CountChildren() int {
	child := tag.firstChild
	if child == nil {
		return 0
	}
	count := 1
	if child.nextSibling == nil {
		return count
	}
	child = child.nextSibling
	for child != nil {
		count++
		child = child.nextSibling
	}
	return count
}

// Count the number of siblings a tag has
func (tag *Tag) CountSiblings() int {
	sib := tag.nextSibling
	if sib == nil {
		return 0
	}
	count := 1
	if sib.nextSibling == nil {
		return count
	}
	sib = sib.nextSibling
	for sib != nil {
		count++
		sib = sib.nextSibling
	}
	return count
}

// Find the last child of the children of a tag
func (tag *Tag) LastChild() *Tag {
	child := tag.firstChild
	for child.nextSibling != nil {
		child = child.nextSibling
	}
	return child
}

// Given the name of a tag, finds the first tag that matches
func (page *Page) GetTag(name string) (*Tag, error) {
	return page.root.GetTag(name)
}

// Find a tag by name, returns an error if not found
// Returns the first tag that matches
func (tag *Tag) GetTag(name string) (*Tag, error) {
	if strings.Index(tag.name, name) == 0 {
		return tag, nil
	}
	couldNotFindError := errors.New("Could not find tag: " + name)
	if tag.CountChildren() == 0 {
		// No children. Not found so far
		return nil, couldNotFindError
	}

	var child *Tag = tag.firstChild
	for child != nil {
		found, err := child.GetTag(name)
		if err == nil {
			return found, err
		}
		child = child.nextSibling
	}

	return nil, couldNotFindError
}

// Generate XML for a tag, recursively
// indent is if the output should be indented or not
// level is the indentation level
// Returns the generated XML as a string
func getXMLRecursively(cursor *Tag, indent bool, level int) string {

	newLine := ""
	if indent {
		newLine = "\n"
	}

	if cursor.CountChildren() == 0 {
		return cursor.getFlatXML(indent, level) + newLine
	}

	content := ""
	xmlContent := ""

	level++

	child := cursor.firstChild
	for child != nil {
		xmlContent = getXMLRecursively(child, indent, level)
		if len(xmlContent) > 0 {
			content += xmlContent
		}
		child = child.nextSibling
	}

	level--

	cursor.xmlContent = cursor.content + content + cursor.lastContent

	ret := cursor.getFlatXML(indent, level)
	if level > 0 {
		ret += newLine
	}
	return ret
}

// Generate CSS for a tag, recursively
// Returns the generated CSS as a string
// The output can go directly in a CSS file
func getCSSRecursively(cursor *Tag) string {
	if cursor.CountChildren() == 0 {
		return cursor.GetCSS()
	}

	style := ""
	cssContent := ""

	child := cursor.firstChild
	for child != nil {
		cssContent = getCSSRecursively(child)
		if len(cssContent) > 0 {
			style += cssContent
		}
		child = child.nextSibling
	}

	return cursor.GetCSS() + style
}

// Generate XML for a page
// The output can go directly in an XML file
func (page *Page) GetXML(indent bool) string {
	return getXMLRecursively(page.root, indent, 0)
}

// Generate CSS for a page
// The output can go directly in a CSS file
func (page *Page) GetCSS() string {
	return getCSSRecursively(page.root)
}

// Generate HTML for a page
func (page *Page) GetHTML() string {
	return page.GetXML(true)
}

// Show various information for a page, used for debugging
func (page *Page) prettyPrint() {
	root := *page.root
	fmt.Println("Page title:", page.title)
	fmt.Println("Page root tag name:", root.name)
	fmt.Println("Root tag children count:", root.CountChildren())
	fmt.Printf("HTML:\n%s\n", page.GetXML(true))
	fmt.Printf("CSS:\n%s\n", page.GetCSS())
}

// Add content to the body tag
// Returns the body tag and nil if successful
// Returns and an error if no body tag is found, else nil
func (page *Page) AddContent(content string) (*Tag, error) {
	body, err := page.root.GetTag("body")
	if err == nil {
		body.AddContent(content)
	}
	return body, err
}

// Get the string for the page
func (page *Page) String() string {
	return page.GetHTML()
}

// Publish the linked HTML and CSS for a Page. Specify if the contents should be generated every time or not.
func (page *Page) Publish(mux *http.ServeMux, htmlurl, cssurl string, generate bool) {
	page.LinkToCSS(cssurl)
	if generate {
		mux.HandleFunc(htmlurl, func(w http.ResponseWriter, req *http.Request) {
			w.Header().Add("Content-Type", "text/html")
			fmt.Fprint(w, page.GetHTML())
		})
		mux.HandleFunc(cssurl, func(w http.ResponseWriter, req *http.Request) {
			w.Header().Add("Content-Type", "text/css")
			fmt.Fprint(w, page.GetCSS())
		})
	} else {
		html := page.GetHTML()
		mux.HandleFunc(htmlurl, func(w http.ResponseWriter, req *http.Request) {
			w.Header().Add("Content-Type", "text/html")
			fmt.Fprint(w, html)
		})
		css := page.GetCSS()
		mux.HandleFunc(cssurl, func(w http.ResponseWriter, req *http.Request) {
			w.Header().Add("Content-Type", "text/css")
			fmt.Fprint(w, css)
		})
	}
}
<|endoftext|>