<commit_before>package devmapper

import (
	"fmt"
	"io/fs"
	"unsafe"

	"golang.org/x/sys/unix"
)

// SectorSize is a device size used for devmapper calculations. Currently this value hardcoded to 512.
const SectorSize = 512

const (
	// ReadOnlyFlag is a devmapper readonly flag value
	ReadOnlyFlag = unix.DM_READONLY_FLAG
)

// Table is a type to represent different devmapper targets like 'zero', 'crypt', ...
type Table interface {
	start() uint64
	length() uint64
	targetType() string
	buildSpec() string // see https://wiki.gentoo.org/wiki/Device-mapper for examples of specs
	openVolume(flag int, perm fs.FileMode) (Volume, error)
}

var errNotImplemented = fmt.Errorf("not implemented")

// Create creates a new device. No table will be loaded. The device will be in
// suspended state. Any IO to this device will fail.
func Create(name string, uuid string) error {
	return ioctlTable(unix.DM_DEV_CREATE, name, uuid, 0, false, nil)
}

// CreateAndLoad creates, loads the provided tables and resumes the device.
func CreateAndLoad(name string, uuid string, flags uint32, tables ...Table) error {
	if err := Create(name, uuid); err != nil {
		return err
	}
	if err := Load(name, flags, tables...); err != nil {
		_ = Remove(name)
		return err
	}
	return Resume(name)
}

// Message passes a message string to the target at specific offset of a device.
func Message(name string, sector int, message string) error {
	return errNotImplemented
}

// Suspend suspends the given device.
func Suspend(name string) error {
	return ioctlTable(unix.DM_DEV_SUSPEND, name, "", unix.DM_SUSPEND_FLAG, false, nil)
}

// Resume resumes the given device.
func Resume(name string) error {
	return ioctlTable(unix.DM_DEV_SUSPEND, name, "", 0, true, nil)
}

// Load loads given table into the device
func Load(name string, flags uint32, tables ...Table) error {
	flags &= unix.DM_READONLY_FLAG
	return ioctlTable(unix.DM_TABLE_LOAD, name, "", flags, false, tables)
}

// Rename renames the device
func Rename(old, new string) error {
	// primaryUdevEvent == true
	return errNotImplemented
}

// SetUUID sets uuid for a given device
func SetUUID(name, uuid string) error {
	return errNotImplemented
}

// Remove remove the device and destroys its tables.
func Remove(name string) error {
	return ioctlTable(unix.DM_DEV_REMOVE, name, "", 0, true, nil)
}

type ListItem struct {
	DevNo uint64
	Name  string
}

func List() ([]ListItem, error) {
	bufferSize := 4096

retry:
	data := make([]byte, bufferSize) // unix.SizeofDmIoctl info + space for output params
	ioctlData := (*unix.DmIoctl)(unsafe.Pointer(&data[0]))
	ioctlData.Version = [...]uint32{4, 0, 0} // minimum required version
	ioctlData.Data_size = uint32(bufferSize)
	ioctlData.Data_start = unix.SizeofDmIoctl

	if err := ioctl(unix.DM_LIST_DEVICES, data); err != nil {
		return nil, err
	}

	if ioctlData.Flags&unix.DM_BUFFER_FULL_FLAG != 0 {
		if bufferSize >= 1024*1024 { // 1 MB
			return nil, fmt.Errorf("ioctl(DM_LIST_DEVICES): output data is too big")

		}
		bufferSize *= 4
		goto retry // retry with bigger buffer
	}

	result := make([]ListItem, 0)

	type dmNameList struct { // reflects struct dm_name_list
		devNo      uint64
		offsetNext uint32
	}
	offset := ioctlData.Data_start
	for offset < ioctlData.Data_size {
		item := (*dmNameList)(unsafe.Pointer(&data[offset]))
		nameData := data[offset+12:] // 12 is sum of the dmNameList fields, at this offset "name" field starts
		if item.offsetNext != 0 {
			nameData = nameData[:item.offsetNext] // make sure that nameData never captures data from the next item
		}
		dev := ListItem{
			DevNo: item.devNo,
			Name:  fixedArrayToString(nameData),
		}
		result = append(result, dev)

		if item.offsetNext == 0 {
			break
		}
		offset += item.offsetNext
	}

	return result, nil
}

// DeviceInfo is a type that holds devmapper device information
type DeviceInfo struct {
	Name       string
	UUID       string
	DevNo      uint64
	OpenCount  int32
	TargetsNum uint32
	Flags      uint32 // combination of unix.DM_*_FLAG
}

// InfoByName returns device information by its name
func InfoByName(name string) (*DeviceInfo, error) {
	return info(name, 0)
}

// InfoByDevno returns device mapper information by its block device number (major/minor)
func InfoByDevno(devno uint64) (*DeviceInfo, error) {
	return info("", devno)
}

func info(name string, devno uint64) (*DeviceInfo, error) {
	data := make([]byte, unix.SizeofDmIoctl)
	ioctlData := (*unix.DmIoctl)(unsafe.Pointer(&data[0]))
	ioctlData.Version = [...]uint32{4, 0, 0} // minimum required version
	copy(ioctlData.Name[:], name)
	ioctlData.Dev = devno
	ioctlData.Data_size = unix.SizeofDmIoctl
	ioctlData.Data_start = unix.SizeofDmIoctl

	if err := ioctl(unix.DM_DEV_STATUS, data); err != nil {
		return nil, err
	}

	const flagsVisibleToUser = unix.DM_READONLY_FLAG | unix.DM_SUSPEND_FLAG | unix.DM_ACTIVE_PRESENT_FLAG | unix.DM_INACTIVE_PRESENT_FLAG | unix.DM_DEFERRED_REMOVE | unix.DM_INTERNAL_SUSPEND_FLAG
	info := DeviceInfo{
		Name:       fixedArrayToString(ioctlData.Name[:]),
		UUID:       fixedArrayToString(ioctlData.Uuid[:]),
		DevNo:      ioctlData.Dev,
		OpenCount:  ioctlData.Open_count,
		TargetsNum: ioctlData.Target_count,
		Flags:      ioctlData.Flags & flagsVisibleToUser,
	}
	return &info, nil
}

// GetVersion returns version for the dm-mapper kernel interface
func GetVersion() (major, minor, patch uint32, err error) {
	data := make([]byte, unix.SizeofDmIoctl)
	ioctlData := (*unix.DmIoctl)(unsafe.Pointer(&data[0]))
	ioctlData.Version = [...]uint32{4, 0, 0} // minimum required version
	ioctlData.Data_size = unix.SizeofDmIoctl
	ioctlData.Data_start = unix.SizeofDmIoctl

	if err := ioctl(unix.DM_VERSION, data); err != nil {
		return 0, 0, 0, err
	}

	return ioctlData.Version[0], ioctlData.Version[1], ioctlData.Version[2], nil
}
<commit_msg>Add comments for public methods/structs<commit_after>package devmapper

import (
	"fmt"
	"io/fs"
	"unsafe"

	"golang.org/x/sys/unix"
)

// SectorSize is a device size used for devmapper calculations. Currently this value hardcoded to 512.
const SectorSize = 512

const (
	// ReadOnlyFlag is a devmapper readonly flag value
	ReadOnlyFlag = unix.DM_READONLY_FLAG
)

// Table is a type to represent different devmapper targets like 'zero', 'crypt', ...
type Table interface {
	start() uint64
	length() uint64
	targetType() string
	buildSpec() string // see https://wiki.gentoo.org/wiki/Device-mapper for examples of specs
	openVolume(flag int, perm fs.FileMode) (Volume, error)
}

var errNotImplemented = fmt.Errorf("not implemented")

// Create creates a new device. No table will be loaded. The device will be in
// suspended state. Any IO to this device will fail.
func Create(name string, uuid string) error {
	return ioctlTable(unix.DM_DEV_CREATE, name, uuid, 0, false, nil)
}

// CreateAndLoad creates, loads the provided tables and resumes the device.
func CreateAndLoad(name string, uuid string, flags uint32, tables ...Table) error {
	if err := Create(name, uuid); err != nil {
		return err
	}
	if err := Load(name, flags, tables...); err != nil {
		_ = Remove(name)
		return err
	}
	return Resume(name)
}

// Message passes a message string to the target at specific offset of a device.
func Message(name string, sector int, message string) error {
	return errNotImplemented
}

// Suspend suspends the given device.
func Suspend(name string) error {
	return ioctlTable(unix.DM_DEV_SUSPEND, name, "", unix.DM_SUSPEND_FLAG, false, nil)
}

// Resume resumes the given device.
func Resume(name string) error {
	return ioctlTable(unix.DM_DEV_SUSPEND, name, "", 0, true, nil)
}

// Load loads given table into the device
func Load(name string, flags uint32, tables ...Table) error {
	flags &= unix.DM_READONLY_FLAG
	return ioctlTable(unix.DM_TABLE_LOAD, name, "", flags, false, tables)
}

// Rename renames the device
func Rename(old, new string) error {
	// primaryUdevEvent == true
	return errNotImplemented
}

// SetUUID sets uuid for a given device
func SetUUID(name, uuid string) error {
	return errNotImplemented
}

// Remove removes the device and destroys its tables.
func Remove(name string) error {
	return ioctlTable(unix.DM_DEV_REMOVE, name, "", 0, true, nil)
}

// ListItem represents information about a dmsetup device
type ListItem struct {
	DevNo uint64
	Name  string
}

// List provides a list of dmsetup devices
func List() ([]ListItem, error) {
	bufferSize := 4096

retry:
	data := make([]byte, bufferSize) // unix.SizeofDmIoctl info + space for output params
	ioctlData := (*unix.DmIoctl)(unsafe.Pointer(&data[0]))
	ioctlData.Version = [...]uint32{4, 0, 0} // minimum required version
	ioctlData.Data_size = uint32(bufferSize)
	ioctlData.Data_start = unix.SizeofDmIoctl

	if err := ioctl(unix.DM_LIST_DEVICES, data); err != nil {
		return nil, err
	}

	if ioctlData.Flags&unix.DM_BUFFER_FULL_FLAG != 0 {
		if bufferSize >= 1024*1024 { // 1 MB
			return nil, fmt.Errorf("ioctl(DM_LIST_DEVICES): output data is too big")

		}
		bufferSize *= 4
		goto retry // retry with bigger buffer
	}

	result := make([]ListItem, 0)

	type dmNameList struct { // reflects struct dm_name_list
		devNo      uint64
		offsetNext uint32
	}
	offset := ioctlData.Data_start
	for offset < ioctlData.Data_size {
		item := (*dmNameList)(unsafe.Pointer(&data[offset]))
		nameData := data[offset+12:] // 12 is sum of the dmNameList fields, at this offset "name" field starts
		if item.offsetNext != 0 {
			nameData = nameData[:item.offsetNext] // make sure that nameData never captures data from the next item
		}
		dev := ListItem{
			DevNo: item.devNo,
			Name:  fixedArrayToString(nameData),
		}
		result = append(result, dev)

		if item.offsetNext == 0 {
			break
		}
		offset += item.offsetNext
	}

	return result, nil
}

// DeviceInfo is a type that holds devmapper device information
type DeviceInfo struct {
	Name       string
	UUID       string
	DevNo      uint64
	OpenCount  int32
	TargetsNum uint32
	Flags      uint32 // combination of unix.DM_*_FLAG
}

// InfoByName returns device information by its name
func InfoByName(name string) (*DeviceInfo, error) {
	return info(name, 0)
}

// InfoByDevno returns device mapper information by its block device number (major/minor)
func InfoByDevno(devno uint64) (*DeviceInfo, error) {
	return info("", devno)
}

func info(name string, devno uint64) (*DeviceInfo, error) {
	data := make([]byte, unix.SizeofDmIoctl)
	ioctlData := (*unix.DmIoctl)(unsafe.Pointer(&data[0]))
	ioctlData.Version = [...]uint32{4, 0, 0} // minimum required version
	copy(ioctlData.Name[:], name)
	ioctlData.Dev = devno
	ioctlData.Data_size = unix.SizeofDmIoctl
	ioctlData.Data_start = unix.SizeofDmIoctl

	if err := ioctl(unix.DM_DEV_STATUS, data); err != nil {
		return nil, err
	}

	const flagsVisibleToUser = unix.DM_READONLY_FLAG | unix.DM_SUSPEND_FLAG | unix.DM_ACTIVE_PRESENT_FLAG | unix.DM_INACTIVE_PRESENT_FLAG | unix.DM_DEFERRED_REMOVE | unix.DM_INTERNAL_SUSPEND_FLAG
	info := DeviceInfo{
		Name:       fixedArrayToString(ioctlData.Name[:]),
		UUID:       fixedArrayToString(ioctlData.Uuid[:]),
		DevNo:      ioctlData.Dev,
		OpenCount:  ioctlData.Open_count,
		TargetsNum: ioctlData.Target_count,
		Flags:      ioctlData.Flags & flagsVisibleToUser,
	}
	return &info, nil
}

// GetVersion returns version for the dm-mapper kernel interface
func GetVersion() (major, minor, patch uint32, err error) {
	data := make([]byte, unix.SizeofDmIoctl)
	ioctlData := (*unix.DmIoctl)(unsafe.Pointer(&data[0]))
	ioctlData.Version = [...]uint32{4, 0, 0} // minimum required version
	ioctlData.Data_size = unix.SizeofDmIoctl
	ioctlData.Data_start = unix.SizeofDmIoctl

	if err := ioctl(unix.DM_VERSION, data); err != nil {
		return 0, 0, 0, err
	}

	return ioctlData.Version[0], ioctlData.Version[1], ioctlData.Version[2], nil
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"log"
	"net/http"

	"code.google.com/p/go.net/websocket"
)

type Message struct {
	Head string `json:"head"`
	Body string `json:"body"`
}

func (self *Message) String() string {
	return self.Head + " with " + self.Body
}

type Client struct {
	id     int
	ws     *websocket.Conn
	server *Server
	ch     chan *Message
	doneCh chan bool
}

func NewClient(ws *websocket.Conn, server *Server) *Client {

	if ws == nil {
		panic("ws cannot be nil")
	}

	if server == nil {
		panic("server cannot be nil")
	}

	maxId++
	ch := make(chan *Message, channelBufSize)
	doneCh := make(chan bool)

	return &Client{maxId, ws, server, ch, doneCh}
}

func (c *Client) Conn() *websocket.Conn {
	return c.ws
}

func (c *Client) Write(msg *Message) {
	select {
	case c.ch <- msg:
	default:
		c.server.Del(c)
		err := fmt.Errorf("client %d is disconnected.", c.id)
		c.server.Err(err)
	}
}

func (c *Client) Done() {
	c.doneCh <- true
}

// Listen Write and Read request via chanel
func (c *Client) Listen() {
	go c.listenWrite()
	c.listenRead()
}

// Listen write request via chanel
func (c *Client) listenWrite() {
	log.Println("Listening write to client")
	for {
		select {

		// send message to the client
		case msg := <-c.ch:
			log.Println("Send:", msg)
			websocket.JSON.Send(c.ws, msg)

		// receive done request
		case <-c.doneCh:
			c.server.Del(c)
			c.doneCh <- true // for listenRead method
			return
		}
	}
}

// Listen read request via chanel
func (c *Client) listenRead() {
	log.Println("Listening read from client")
	for {
		select {

		// receive done request
		case <-c.doneCh:
			c.server.Del(c)
			c.doneCh <- true // for listenWrite method
			return

		// read data from websocket connection
		default:
			var msg Message
			err := websocket.JSON.Receive(c.ws, &msg)
			if err == io.EOF {
				c.doneCh <- true
			} else if err != nil {
				c.server.Err(err)
			} else {
				c.server.SendAll(&msg)
			}
		}
	}
}

type Server struct {
	pattern   string
	messages  []*Message
	clients   map[int]*Client
	addCh     chan *Client
	delCh     chan *Client
	sendAllCh chan *Message
	doneCh    chan bool
	errCh     chan error
}

// Create new chat server.
func NewServer(pattern string) *Server {
	messages := []*Message{}
	clients := make(map[int]*Client)
	addCh := make(chan *Client)
	delCh := make(chan *Client)
	sendAllCh := make(chan *Message)
	doneCh := make(chan bool)
	errCh := make(chan error)

	return &Server{
		pattern,
		messages,
		clients,
		addCh,
		delCh,
		sendAllCh,
		doneCh,
		errCh,
	}
}

func (s *Server) Add(c *Client) {
	s.addCh <- c
}

func (s *Server) Del(c *Client) {
	s.delCh <- c
}

func (s *Server) SendAll(msg *Message) {
	s.sendAllCh <- msg
}

func (s *Server) Done() {
	s.doneCh <- true
}

func (s *Server) Err(err error) {
	s.errCh <- err
}

func (s *Server) sendPastMessages(c *Client) {
	for _, msg := range s.messages {
		c.Write(msg)
	}
}

func (s *Server) sendAll(msg *Message) {
	for _, c := range s.clients {
		c.Write(msg)
	}
}

// Listen and serve.
// It serves client connection and broadcast request.
func (s *Server) Listen() {

	log.Println("Listening server...")

	// websocket handler
	onConnected := func(ws *websocket.Conn) {
		defer func() {
			err := ws.Close()
			if err != nil {
				s.errCh <- err
			}
		}()

		client := NewClient(ws, s)
		s.Add(client)
		client.Listen()
	}
	http.Handle(s.pattern, websocket.Handler(onConnected))
	log.Println("Created handler")

	for {
		select {

		// Add new a client
		case c := <-s.addCh:
			log.Println("Added new client")
			s.clients[c.id] = c
			log.Println("Now", len(s.clients), "clients connected.")
			s.sendPastMessages(c)

		// del a client
		case c := <-s.delCh:
			log.Println("Delete client")
			delete(s.clients, c.id)

		// broadcast message for all clients
		case msg := <-s.sendAllCh:
			log.Println("Send all:", msg)
			s.messages = append(s.messages, msg)
			s.sendAll(msg)

		case err := <-s.errCh:
			log.Println("Error:", err.Error())

		case <-s.doneCh:
			return
		}
	}
}

// func listen() {
// 	log.Println("Listening ws server on /websocket ...")

// 	// websocket handler
// 	onConnected := func(ws *websocket.Conn) {
// 		defer func() {
// 			err := ws.Close()
// 			if err != nil {
// 				log.Println("Close Error:", err)
// 			}
// 		}()
// 		log.Println("Created onConnected handler")
// 		log.Printf("onConnected ws: %v\n", ws)

// 		//		websocket.JSON.Send(ws, Message{"hello", "client"})
// 		log.Println("Listening receiving from client")
// 		for {
// 			select {
// 			default:

// 				var msg Message
// 				err := websocket.JSON.Receive(ws, &msg)
// 				if err == io.EOF {
// 					log.Println("Connection closed:", err)
// 					return
// 				} else if err != nil {
// 					log.Println("Receive Error:", err)
// 				} else {
// 					log.Println("Received: ", msg.String())
// 					if msg.Head == "ping" {
// 						websocket.JSON.Send(ws, Message{"pong", "pong"})
// 					}
// 				}
// 			}
// 		}

// 	}
// 	http.Handle("/websocket", websocket.Handler(onConnected))

// }

func main() {

	// websocket server
	server := chat.NewServer("/websocket")
	go server.Listen()

	// static files
	http.Handle("/", http.FileServer(http.Dir("static")))

	log.Println("Listening web server on :8044 ...")
	log.Fatal(http.ListenAndServe(":8044", nil))
}
<commit_msg>cleanup<commit_after>package main

import (
	"io"
	"log"
	"net/http"

	"code.google.com/p/go.net/websocket"
)

type Message struct {
	Head string `json:"head"`
	Body string `json:"body"`
}

func (self *Message) String() string {
	return self.Head + " with " + self.Body
}

type Client struct {
	id     int
	ws     *websocket.Conn
	server *Server
	ch     chan *Message
	doneCh chan bool
}

func NewClient(ws *websocket.Conn, server *Server) *Client {

	if ws == nil {
		panic("ws cannot be nil")
	}

	if server == nil {
		panic("server cannot be nil")
	}

	maxId++
	ch := make(chan *Message, channelBufSize)
	doneCh := make(chan bool)

	return &Client{maxId, ws, server, ch, doneCh}
}

func (c *Client) Conn() *websocket.Conn {
	return c.ws
}

func (c *Client) Write(msg *Message) {
	select {
	case c.ch <- msg:
	default:
		c.server.Del(c)
		err := fmt.Errorf("client %d is disconnected.", c.id)
		c.server.Err(err)
	}
}

func (c *Client) Done() {
	c.doneCh <- true
}

// Listen Write and Read request via chanel
func (c *Client) Listen() {
	go c.listenWrite()
	c.listenRead()
}

// Listen write request via chanel
func (c *Client) listenWrite() {
	log.Println("Listening write to client")
	for {
		select {

		// send message to the client
		case msg := <-c.ch:
			log.Println("Send:", msg)
			websocket.JSON.Send(c.ws, msg)

		// receive done request
		case <-c.doneCh:
			c.server.Del(c)
			c.doneCh <- true // for listenRead method
			return
		}
	}
}

// Listen read request via chanel
func (c *Client) listenRead() {
	log.Println("Listening read from client")
	for {
		select {

		// receive done request
		case <-c.doneCh:
			c.server.Del(c)
			c.doneCh <- true // for listenWrite method
			return

		// read data from websocket connection
		default:
			var msg Message
			err := websocket.JSON.Receive(c.ws, &msg)
			if err == io.EOF {
				c.doneCh <- true
			} else if err != nil {
				c.server.Err(err)
			} else {
				c.server.SendAll(&msg)
			}
		}
	}
}

type Server struct {
	pattern   string
	messages  []*Message
	clients   map[int]*Client
	addCh     chan *Client
	delCh     chan *Client
	sendAllCh chan *Message
	doneCh    chan bool
	errCh     chan error
}

// Create new chat server.
func NewServer(pattern string) *Server {
	messages := []*Message{}
	clients := make(map[int]*Client)
	addCh := make(chan *Client)
	delCh := make(chan *Client)
	sendAllCh := make(chan *Message)
	doneCh := make(chan bool)
	errCh := make(chan error)

	return &Server{
		pattern,
		messages,
		clients,
		addCh,
		delCh,
		sendAllCh,
		doneCh,
		errCh,
	}
}

func (s *Server) Add(c *Client) {
	s.addCh <- c
}

func (s *Server) Del(c *Client) {
	s.delCh <- c
}

func (s *Server) SendAll(msg *Message) {
	s.sendAllCh <- msg
}

func (s *Server) Done() {
	s.doneCh <- true
}

func (s *Server) Err(err error) {
	s.errCh <- err
}

func (s *Server) sendPastMessages(c *Client) {
	for _, msg := range s.messages {
		c.Write(msg)
	}
}

func (s *Server) sendAll(msg *Message) {
	for _, c := range s.clients {
		c.Write(msg)
	}
}

// Listen and serve.
// It serves client connection and broadcast request.
func (s *Server) Listen() {

	log.Println("Listening server...")

	// websocket handler
	onConnected := func(ws *websocket.Conn) {
		defer func() {
			err := ws.Close()
			if err != nil {
				s.errCh <- err
			}
		}()

		client := NewClient(ws, s)
		s.Add(client)
		client.Listen()
	}
	http.Handle(s.pattern, websocket.Handler(onConnected))
	log.Println("Created handler")

	for {
		select {

		// Add new a client
		case c := <-s.addCh:
			log.Println("Added new client")
			s.clients[c.id] = c
			log.Println("Now", len(s.clients), "clients connected.")
			s.sendPastMessages(c)

		// del a client
		case c := <-s.delCh:
			log.Println("Delete client")
			delete(s.clients, c.id)

		// broadcast message for all clients
		case msg := <-s.sendAllCh:
			log.Println("Send all:", msg)
			s.messages = append(s.messages, msg)
			s.sendAll(msg)

		case err := <-s.errCh:
			log.Println("Error:", err.Error())

		case <-s.doneCh:
			return
		}
	}
}

func main() {

	// websocket server
	server := chat.NewServer("/websocket")
	go server.Listen()

	// static files
	http.Handle("/", http.FileServer(http.Dir("static")))

	log.Println("Listening web server on :8044 ...")
	log.Fatal(http.ListenAndServe(":8044", nil))
}
<|endoftext|>


<commit_before>package horeb

import (
	"errors"
	"fmt"
	"math/rand"
	"sort"
	"strconv"
)

// UnicodeBlock values represent a contiguous range of Unicode codepoints.
type UnicodeBlock struct {
	Start, End rune
}

// Blocks is a map of short string labels to UnicodeBlock values.
var Blocks = map[string]UnicodeBlock{

	// Basic Multilingual Plane (0000-ffff)
	// https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
	"hebrew":         UnicodeBlock{0x0590, 0x05ff},
	"currency":       UnicodeBlock{0x20a0, 0x20cf},
	"letterlike":     UnicodeBlock{0x2100, 0x214f},
	"misc_technical": UnicodeBlock{0x2300, 0x23ff},
	"geometric":      UnicodeBlock{0x25a0, 0x25ff},
	"misc_symbols":   UnicodeBlock{0x2600, 0x26ff},
	"dingbats":       UnicodeBlock{0x2700, 0x27bf},

	// Supplementary Multilingual Plane (10000-1ffff)
	// https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane
	"aegean_nums":        UnicodeBlock{0x10100, 0x1013f},
	"ancient_greek_nums": UnicodeBlock{0x10140, 0x1018f},
	"phaistos_disc":      UnicodeBlock{0x101d0, 0x101ff},
	"math_alnum":         UnicodeBlock{0x1d400, 0x1d7ff},
	"emoji":              UnicodeBlock{0x1f300, 0x1f5ff},
	"mahjong":            UnicodeBlock{0x1f000, 0x1f02f},
	"dominos":            UnicodeBlock{0x1f030, 0x1f09f},
	"playing_cards":      UnicodeBlock{0x1f0a0, 0x1f0ff},
}

// RandomBlock returns a UnicodeBlock at random from a map[string]UnicodeBlock provided as argument.
func RandomBlock(m map[string]UnicodeBlock) (UnicodeBlock, error) {
	nkeys := len(m)
	if nkeys < 1 {
		return UnicodeBlock{}, errors.New("Empty map provided")
	}
	var keys []string
	for k := range m {
		keys = append(keys, k)
	}
	randKey := keys[rand.Intn(nkeys)]
	return m[randKey], nil
}

func PrintBlocks(all bool) {
	// Create a slice of alphabetically-sorted keys.
	var keys []string
	for k := range Blocks {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		b := Blocks[k]
		fmt.Printf("%5x %5x  %s\n", b.Start, b.End, k)
		if all {
			b.Print()
			fmt.Println()
		}
	}
}

// RandomRune returns a single rune at random from UnicodeBlock.
func (b UnicodeBlock) RandomRune() rune {
	return rune(rand.Intn(int(b.End-b.Start)) + int(b.Start) + 1)
}

// Print prints all printable runes in UnicodeBlock.
func (b UnicodeBlock) Print() {
	for i := b.Start; i <= b.End; i++ {

		// Only print printable runes.
		if !strconv.IsPrint(i) {
			continue
		}

		fmt.Printf("%c ", i)
	}
	fmt.Println()
}

// PrintRandom prints n random runes from UnicodeBlock.
func (b UnicodeBlock) PrintRandom(n int, ofs string) {
	for i := 0; i < n; i++ {
		fmt.Printf("%c%s", b.RandomRune(), ofs)
	}
	fmt.Println()
}
<commit_msg>Add arrows block<commit_after>package horeb

import (
	"errors"
	"fmt"
	"math/rand"
	"sort"
	"strconv"
)

// UnicodeBlock values represent a contiguous range of Unicode codepoints.
type UnicodeBlock struct {
	Start, End rune
}

// Blocks is a map of short string labels to UnicodeBlock values.
var Blocks = map[string]UnicodeBlock{

	// Basic Multilingual Plane (0000-ffff)
	// https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
	"hebrew":         UnicodeBlock{0x0590, 0x05ff},
	"currency":       UnicodeBlock{0x20a0, 0x20cf},
	"letterlike":     UnicodeBlock{0x2100, 0x214f},
	"arrows":         UnicodeBlock{0x2190, 0x21ff},
	"misc_technical": UnicodeBlock{0x2300, 0x23ff},
	"geometric":      UnicodeBlock{0x25a0, 0x25ff},
	"misc_symbols":   UnicodeBlock{0x2600, 0x26ff},
	"dingbats":       UnicodeBlock{0x2700, 0x27bf},

	// Supplementary Multilingual Plane (10000-1ffff)
	// https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane
	"aegean_nums":        UnicodeBlock{0x10100, 0x1013f},
	"ancient_greek_nums": UnicodeBlock{0x10140, 0x1018f},
	"phaistos_disc":      UnicodeBlock{0x101d0, 0x101ff},
	"math_alnum":         UnicodeBlock{0x1d400, 0x1d7ff},
	"emoji":              UnicodeBlock{0x1f300, 0x1f5ff},
	"mahjong":            UnicodeBlock{0x1f000, 0x1f02f},
	"dominos":            UnicodeBlock{0x1f030, 0x1f09f},
	"playing_cards":      UnicodeBlock{0x1f0a0, 0x1f0ff},
}

// RandomBlock returns a UnicodeBlock at random from a map[string]UnicodeBlock provided as argument.
func RandomBlock(m map[string]UnicodeBlock) (UnicodeBlock, error) {
	nkeys := len(m)
	if nkeys < 1 {
		return UnicodeBlock{}, errors.New("Empty map provided")
	}
	var keys []string
	for k := range m {
		keys = append(keys, k)
	}
	randKey := keys[rand.Intn(nkeys)]
	return m[randKey], nil
}

func PrintBlocks(all bool) {
	// Create a slice of alphabetically-sorted keys.
	var keys []string
	for k := range Blocks {
		keys = append(keys, k)
	}
	sort.Strings(keys)

	for _, k := range keys {
		b := Blocks[k]
		fmt.Printf("%5x %5x  %s\n", b.Start, b.End, k)
		if all {
			b.Print()
			fmt.Println()
		}
	}
}

// RandomRune returns a single rune at random from UnicodeBlock.
func (b UnicodeBlock) RandomRune() rune {
	return rune(rand.Intn(int(b.End-b.Start)) + int(b.Start) + 1)
}

// Print prints all printable runes in UnicodeBlock.
func (b UnicodeBlock) Print() {
	for i := b.Start; i <= b.End; i++ {

		// Only print printable runes.
		if !strconv.IsPrint(i) {
			continue
		}

		fmt.Printf("%c ", i)
	}
	fmt.Println()
}

// PrintRandom prints n random runes from UnicodeBlock.
func (b UnicodeBlock) PrintRandom(n int, ofs string) {
	for i := 0; i < n; i++ {
		fmt.Printf("%c%s", b.RandomRune(), ofs)
	}
	fmt.Println()
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/dotcloud/docker"
	"github.com/dotcloud/docker/engine"
	"github.com/dotcloud/docker/sysinit"
	"github.com/dotcloud/docker/utils"
	"log"
	"os"
	"strings"
)

var (
	GITCOMMIT string
	VERSION   string
)

func main() {
	if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
		// Running in init mode
		sysinit.SysInit()
		return
	}
	// FIXME: Switch d and D ? (to be more sshd like)
	flVersion := flag.Bool("v", false, "Print version information and quit")
	flDaemon := flag.Bool("d", false, "Daemon mode")
	flDebug := flag.Bool("D", false, "Debug mode")
	flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
	flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.")
	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
	flDns := flag.String("dns", "", "Set custom dns servers")
	flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
	flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
	flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
	flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
	flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
	flGraphDriver := flag.String("graph-driver", "", "For docker to use a specific graph driver")

	flag.Parse()

	if *flVersion {
		showVersion()
		return
	}
	if len(flHosts) > 1 {
		flHosts = flHosts[1:] //trick to display a nice default value in the usage
	}
	for i, flHost := range flHosts {
		host, err := utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
		if err == nil {
			flHosts[i] = host
		} else {
			log.Fatal(err)
		}
	}

	if *flDebug {
		os.Setenv("DEBUG", "1")
	}
	docker.GITCOMMIT = GITCOMMIT
	docker.VERSION = VERSION
	if *flDaemon {
		if flag.NArg() != 0 {
			flag.Usage()
			return
		}
		eng, err := engine.New(*flRoot)
		if err != nil {
			log.Fatal(err)
		}
		// Load plugin: httpapi
		job := eng.Job("initapi")
		job.Setenv("Pidfile", *pidfile)
		job.Setenv("Root", *flRoot)
		job.SetenvBool("AutoRestart", *flAutoRestart)
		job.SetenvBool("EnableCors", *flEnableCors)
		job.Setenv("Dns", *flDns)
		job.SetenvBool("EnableIptables", *flEnableIptables)
		job.Setenv("BridgeIface", *bridgeName)
		job.Setenv("DefaultIp", *flDefaultIp)
		job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
		job.Setenv("GraphDriver", *flGraphDriver)
		if err := job.Run(); err != nil {
			log.Fatal(err)
		}
		// Serve api
		job = eng.Job("serveapi", flHosts...)
		job.SetenvBool("Logging", true)
		if err := job.Run(); err != nil {
			log.Fatal(err)
		}
	} else {
		if len(flHosts) > 1 {
			log.Fatal("Please specify only one -H")
		}
		protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
		if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
			if sterr, ok := err.(*utils.StatusError); ok {
				os.Exit(sterr.Status)
			}
			log.Fatal(err)
		}
	}
}

func showVersion() {
	fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
}
<commit_msg>Update a few flag help strings for consistency and clarity<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/dotcloud/docker"
	"github.com/dotcloud/docker/engine"
	"github.com/dotcloud/docker/sysinit"
	"github.com/dotcloud/docker/utils"
	"log"
	"os"
	"strings"
)

var (
	GITCOMMIT string
	VERSION   string
)

func main() {
	if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" {
		// Running in init mode
		sysinit.SysInit()
		return
	}
	// FIXME: Switch d and D ? (to be more sshd like)
	flVersion := flag.Bool("v", false, "Print version information and quit")
	flDaemon := flag.Bool("d", false, "Enable daemon mode")
	flDebug := flag.Bool("D", false, "Enable debug mode")
	flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking")
	pidfile := flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file")
	flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime")
	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API")
	flDns := flag.String("dns", "", "Force docker to use specific DNS servers")
	flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
	flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise")
	flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules")
	flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports")
	flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
	flGraphDriver := flag.String("graph-driver", "", "Force docker runtime to use a specific graph driver")

	flag.Parse()

	if *flVersion {
		showVersion()
		return
	}
	if len(flHosts) > 1 {
		flHosts = flHosts[1:] //trick to display a nice default value in the usage
	}
	for i, flHost := range flHosts {
		host, err := utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
		if err == nil {
			flHosts[i] = host
		} else {
			log.Fatal(err)
		}
	}

	if *flDebug {
		os.Setenv("DEBUG", "1")
	}
	docker.GITCOMMIT = GITCOMMIT
	docker.VERSION = VERSION
	if *flDaemon {
		if flag.NArg() != 0 {
			flag.Usage()
			return
		}
		eng, err := engine.New(*flRoot)
		if err != nil {
			log.Fatal(err)
		}
		// Load plugin: httpapi
		job := eng.Job("initapi")
		job.Setenv("Pidfile", *pidfile)
		job.Setenv("Root", *flRoot)
		job.SetenvBool("AutoRestart", *flAutoRestart)
		job.SetenvBool("EnableCors", *flEnableCors)
		job.Setenv("Dns", *flDns)
		job.SetenvBool("EnableIptables", *flEnableIptables)
		job.Setenv("BridgeIface", *bridgeName)
		job.Setenv("DefaultIp", *flDefaultIp)
		job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
		job.Setenv("GraphDriver", *flGraphDriver)
		if err := job.Run(); err != nil {
			log.Fatal(err)
		}
		// Serve api
		job = eng.Job("serveapi", flHosts...)
		job.SetenvBool("Logging", true)
		if err := job.Run(); err != nil {
			log.Fatal(err)
		}
	} else {
		if len(flHosts) > 1 {
			log.Fatal("Please specify only one -H")
		}
		protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
		if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
			if sterr, ok := err.(*utils.StatusError); ok {
				os.Exit(sterr.Status)
			}
			log.Fatal(err)
		}
	}
}

func showVersion() {
	fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
}
<|endoftext|>


<commit_before>package docker

import (
	"bufio"
	"bytes"
	"context"
	"github.com/ViBiOh/docker-deploy/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/client"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
)

const host = `DOCKER_HOST`
const version = `DOCKER_VERSION`
const configurationFile = `./users`

var commaByte = []byte(`,`)
var splitLogs = regexp.MustCompile(`.{8}(.*?)\n`)

var networkConfig = network.NetworkingConfig{
	EndpointsConfig: map[string]*network.EndpointSettings{
		`traefik`: &network.EndpointSettings{},
	},
}

var containersRequest = regexp.MustCompile(`/containers/?$`)
var containerRequest = regexp.MustCompile(`/containers/([^/]+)/?$`)
var startRequest = regexp.MustCompile(`/containers/([^/]+)/start`)
var stopRequest = regexp.MustCompile(`/containers/([^/]+)/stop`)
var restartRequest = regexp.MustCompile(`/containers/([^/]+)/restart`)
var logRequest = regexp.MustCompile(`/containers/([^/]+)/logs`)

type results struct {
	Results interface{} `json:"results"`
}

type user struct {
	username string
	password string
}

type dockerCompose struct {
	Version  string
	Services map[string]struct {
		Image       string
		Command     string
		Environment map[string]string
		Labels      map[string]string
		ReadOnly    bool  `yaml:"read_only"`
		CPUShares   int64 `yaml:"cpu_shares"`
		MemoryLimit int64 `yaml:"mem_limit"`
	}
}

var docker *client.Client
var users map[string]*user

func errorHandler(w http.ResponseWriter, err error) {
	log.Print(err)
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

func readConfiguration(path string) map[string]*user {
	configFile, err := os.Open(path)
	defer configFile.Close()

	if err != nil {
		log.Print(err)
		return nil
	}

	users := make(map[string]*user)

	scanner := bufio.NewScanner(configFile)
	for scanner.Scan() {
		parts := bytes.Split(scanner.Bytes(), commaByte)
		user := user{string(parts[0]), string(parts[1])}

		users[strings.ToLower(user.username)] = &user
	}

	return users
}

func init() {
	users = readConfiguration(configurationFile)

	client, err := client.NewClient(os.Getenv(host), os.Getenv(version), nil, nil)
	if err != nil {
		log.Fatal(err)
	} else {
		docker = client
	}
}

func inspectContainerHandler(w http.ResponseWriter, containerID []byte) {
	if container, err := docker.ContainerInspect(context.Background(), string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, container)
	}
}

func startContainer(containerID string) error {
	return docker.ContainerStart(context.Background(), string(containerID), types.ContainerStartOptions{})
}

func startContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := startContainer(string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func stopContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerStop(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func restartContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerRestart(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func logContainerHandler(w http.ResponseWriter, containerID []byte) {
	logs, err := docker.ContainerLogs(context.Background(), string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: false})
	if err != nil {
		errorHandler(w, err)
		return
	}

	defer logs.Close()

	if logLines, err := ioutil.ReadAll(logs); err != nil {
		errorHandler(w, err)
	} else {
		matches := splitLogs.FindAllSubmatch(logLines, -1)
		cleanLogs := make([]string, 0, len(matches))
		for _, match := range matches {
			cleanLogs = append(cleanLogs, string(match[1]))
		}

		jsonHttp.ResponseJSON(w, results{cleanLogs})
	}
}

func listContainersHandler(w http.ResponseWriter) {
	if containers, err := docker.ContainerList(context.Background(), types.ContainerListOptions{All: true}); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, results{containers})
	}
}

func readBody(body io.ReadCloser) ([]byte, error) {
	defer body.Close()
	return ioutil.ReadAll(body)
}

func runComposeHandler(w http.ResponseWriter, name []byte, composeFile []byte) {
	compose := dockerCompose{}

	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, err)
		return
	}

	ids := make([]string, len(compose.Services))
	for serviceName, service := range compose.Services {
		if _, err := docker.ImagePull(context.Background(), service.Image, types.ImagePullOptions{}); err != nil {
			errorHandler(w, err)
			return
		}

		environments := make([]string, len(service.Environment))
		for key, value := range service.Environment {
			environments = append(environments, key+`=`+value)
		}

		config := container.Config{
			Image:  service.Image,
			Labels: service.Labels,
			Env:    environments,
		}

		if service.Command != `` {
			config.Cmd = strslice.StrSlice([]string{service.Command})
		}

		var hostConfig = container.HostConfig{
			LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
				`max-size`: `50m`,
			}},
			RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
			Resources: container.Resources{
				CPUShares: 128,
				Memory:    134217728,
			},
			SecurityOpt: []string{`no-new-privileges`},
		}

		if service.ReadOnly {
			hostConfig.ReadonlyRootfs = service.ReadOnly
		}

		if service.CPUShares != 0 {
			hostConfig.Resources.CPUShares = service.CPUShares
		}

		if service.MemoryLimit != 0 {
			hostConfig.Resources.Memory = service.MemoryLimit
		}

		id, err := docker.ContainerCreate(context.Background(), &config, &hostConfig, &networkConfig, string(name)+`_`+serviceName)
		if err != nil {
			errorHandler(w, err)
			return
		}

		startContainer(id.ID)
		ids = append(ids, id.ID)
	}

	jsonHttp.ResponseJSON(w, results{ids})
}

func isAuthenticated(r *http.Request) bool {
	username, password, ok := r.BasicAuth()

	if ok {
		user, ok := users[strings.ToLower(username)]

		if ok && user.password == password {
			return true
		}
	}

	return false
}

func unauthorized(w http.ResponseWriter) {
	http.Error(w, `Authentication required`, http.StatusUnauthorized)
}

// Handler for Hello request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type, Authorization`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET, POST`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	if r.Method == http.MethodOptions {
		w.Write(nil)
		return
	}

	urlPath := []byte(r.URL.Path)

	if containersRequest.Match(urlPath) && r.Method == http.MethodGet {
		listContainersHandler(w)
	} else if isAuthenticated(r) {
		if containerRequest.Match(urlPath) && r.Method == http.MethodPost {
			if composeBody, err := readBody(r.Body); err != nil {
				errorHandler(w, err)
			} else {
				runComposeHandler(w, containerRequest.FindSubmatch(urlPath)[1], composeBody)
			}
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodGet {
			inspectContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
		} else if startRequest.Match(urlPath) && r.Method == http.MethodPost {
			startContainerHandler(w, startRequest.FindSubmatch(urlPath)[1])
		} else if stopRequest.Match(urlPath) && r.Method == http.MethodPost {
			stopContainerHandler(w, stopRequest.FindSubmatch(urlPath)[1])
		} else if restartRequest.Match(urlPath) && r.Method == http.MethodPost {
			restartContainerHandler(w, restartRequest.FindSubmatch(urlPath)[1])
		} else if logRequest.Match(urlPath) && r.Method == http.MethodGet {
			logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
		}
	} else {
		unauthorized(w)
	}
}
<commit_msg>Refactoring<commit_after>package docker

import (
	"bufio"
	"bytes"
	"context"
	"github.com/ViBiOh/docker-deploy/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/client"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
)

const host = `DOCKER_HOST`
const version = `DOCKER_VERSION`
const configurationFile = `./users`

var commaByte = []byte(`,`)
var splitLogs = regexp.MustCompile(`.{8}(.*?)\n`)

var networkConfig = network.NetworkingConfig{
	EndpointsConfig: map[string]*network.EndpointSettings{
		`traefik`: &network.EndpointSettings{},
	},
}

var containersRequest = regexp.MustCompile(`/containers/?$`)
var containerRequest = regexp.MustCompile(`/containers/([^/]+)/?$`)
var startRequest = regexp.MustCompile(`/containers/([^/]+)/start`)
var stopRequest = regexp.MustCompile(`/containers/([^/]+)/stop`)
var restartRequest = regexp.MustCompile(`/containers/([^/]+)/restart`)
var logRequest = regexp.MustCompile(`/containers/([^/]+)/logs`)

type results struct {
	Results interface{} `json:"results"`
}

type user struct {
	username string
	password string
}

type dockerComposeService struct {
	Image       string
	Command     string
	Environment map[string]string
	Labels      map[string]string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

var docker *client.Client
var users map[string]*user

func errorHandler(w http.ResponseWriter, err error) {
	log.Print(err)
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

func readConfiguration(path string) map[string]*user {
	configFile, err := os.Open(path)
	defer configFile.Close()

	if err != nil {
		log.Print(err)
		return nil
	}

	users := make(map[string]*user)

	scanner := bufio.NewScanner(configFile)
	for scanner.Scan() {
		parts := bytes.Split(scanner.Bytes(), commaByte)
		user := user{string(parts[0]), string(parts[1])}

		users[strings.ToLower(user.username)] = &user
	}

	return users
}

func init() {
	users = readConfiguration(configurationFile)

	client, err := client.NewClient(os.Getenv(host), os.Getenv(version), nil, nil)
	if err != nil {
		log.Fatal(err)
	} else {
		docker = client
	}
}

func inspectContainerHandler(w http.ResponseWriter, containerID []byte) {
	if container, err := docker.ContainerInspect(context.Background(), string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, container)
	}
}

func startContainer(containerID string) error {
	return docker.ContainerStart(context.Background(), string(containerID), types.ContainerStartOptions{})
}

func startContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := startContainer(string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func stopContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerStop(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func restartContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerRestart(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func logContainerHandler(w http.ResponseWriter, containerID []byte) {
	logs, err := docker.ContainerLogs(context.Background(), string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: false})
	if err != nil {
		errorHandler(w, err)
		return
	}

	defer logs.Close()

	if logLines, err := ioutil.ReadAll(logs); err != nil {
		errorHandler(w, err)
	} else {
		matches := splitLogs.FindAllSubmatch(logLines, -1)
		cleanLogs := make([]string, 0, len(matches))
		for _, match := range matches {
			cleanLogs = append(cleanLogs, string(match[1]))
		}

		jsonHttp.ResponseJSON(w, results{cleanLogs})
	}
}

func listContainersHandler(w http.ResponseWriter) {
	if containers, err := docker.ContainerList(context.Background(), types.ContainerListOptions{All: true}); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, results{containers})
	}
}

func readBody(body io.ReadCloser) ([]byte, error) {
	defer body.Close()
	return ioutil.ReadAll(body)
}

func getConfig(service *dockerComposeService) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if service.Command != `` {
		config.Cmd = strslice.StrSlice([]string{service.Command})
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    134217728,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		hostConfig.Resources.Memory = service.MemoryLimit
	}

	return &hostConfig
}

func runComposeHandler(w http.ResponseWriter, name []byte, composeFile []byte) {
	compose := dockerCompose{}

	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, err)
		return
	}

	ids := make([]string, len(compose.Services))
	for serviceName, service := range compose.Services {
		if _, err := docker.ImagePull(context.Background(), service.Image, types.ImagePullOptions{}); err != nil {
			errorHandler(w, err)
			return
		}

		id, err := docker.ContainerCreate(context.Background(), getConfig(&service), getHostConfig(&service), &networkConfig, string(name)+`_`+serviceName)
		if err != nil {
			errorHandler(w, err)
			return
		}

		startContainer(id.ID)
		ids = append(ids, id.ID)
	}

	jsonHttp.ResponseJSON(w, results{ids})
}

func isAuthenticated(r *http.Request) bool {
	username, password, ok := r.BasicAuth()

	if ok {
		user, ok := users[strings.ToLower(username)]

		if ok && user.password == password {
			return true
		}
	}

	return false
}

func unauthorized(w http.ResponseWriter) {
	http.Error(w, `Authentication required`, http.StatusUnauthorized)
}

// Handler for Hello request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type, Authorization`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET, POST`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	if r.Method == http.MethodOptions {
		w.Write(nil)
		return
	}

	urlPath := []byte(r.URL.Path)

	if containersRequest.Match(urlPath) && r.Method == http.MethodGet {
		listContainersHandler(w)
	} else if isAuthenticated(r) {
		if containerRequest.Match(urlPath) && r.Method == http.MethodPost {
			if composeBody, err := readBody(r.Body); err != nil {
				errorHandler(w, err)
			} else {
				runComposeHandler(w, containerRequest.FindSubmatch(urlPath)[1], composeBody)
			}
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodGet {
			inspectContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
		} else if startRequest.Match(urlPath) && r.Method == http.MethodPost {
			startContainerHandler(w, startRequest.FindSubmatch(urlPath)[1])
		} else if stopRequest.Match(urlPath) && r.Method == http.MethodPost {
			stopContainerHandler(w, stopRequest.FindSubmatch(urlPath)[1])
		} else if restartRequest.Match(urlPath) && r.Method == http.MethodPost {
			restartContainerHandler(w, restartRequest.FindSubmatch(urlPath)[1])
		} else if logRequest.Match(urlPath) && r.Method == http.MethodGet {
			logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
		}
	} else {
		unauthorized(w)
	}
}
<|endoftext|>


<commit_before>package docker

import (
	"bufio"
	"bytes"
	"context"
	"github.com/ViBiOh/docker-deploy/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/client"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
)

const host = `DOCKER_HOST`
const version = `DOCKER_VERSION`
const configurationFile = `./users`

var commaByte = []byte(`,`)
var splitLogs = regexp.MustCompile(`.{8}(.*?)\n`)

var networkConfig = network.NetworkingConfig{
	EndpointsConfig: map[string]*network.EndpointSettings{
		`traefik`: &network.EndpointSettings{},
	},
}

var containersRequest = regexp.MustCompile(`/containers/?$`)
var containerRequest = regexp.MustCompile(`/containers/([^/]+)/?$`)
var startRequest = regexp.MustCompile(`/containers/([^/]+)/start`)
var stopRequest = regexp.MustCompile(`/containers/([^/]+)/stop`)
var restartRequest = regexp.MustCompile(`/containers/([^/]+)/restart`)
var logRequest = regexp.MustCompile(`/containers/([^/]+)/logs`)

type results struct {
	Results interface{} `json:"results"`
}

type user struct {
	username string
	password string
	role     string
}

type dockerComposeService struct {
	Image       string
	Command     string
	Environment map[string]string
	Labels      map[string]string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

var docker *client.Client
var users map[string]*user

func errorHandler(w http.ResponseWriter, err error) {
	log.Print(err)
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

func init() {
	users = readConfiguration(configurationFile)

	client, err := client.NewClient(os.Getenv(host), os.Getenv(version), nil, nil)
	if err != nil {
		log.Fatal(err)
	} else {
		docker = client
	}
}

func readConfiguration(path string) map[string]*user {
	configFile, err := os.Open(path)
	defer configFile.Close()

	if err != nil {
		log.Print(err)
		return nil
	}

	users := make(map[string]*user)

	scanner := bufio.NewScanner(configFile)
	for scanner.Scan() {
		parts := bytes.Split(scanner.Bytes(), commaByte)
		user := user{string(parts[0]), string(parts[1]), string(parts[2])}

		users[strings.ToLower(user.username)] = &user
	}

	return users
}

func inspectContainerHandler(w http.ResponseWriter, containerID []byte) {
	if container, err := docker.ContainerInspect(context.Background(), string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, container)
	}
}

func startContainer(containerID string) error {
	return docker.ContainerStart(context.Background(), string(containerID), types.ContainerStartOptions{})
}

func startContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := startContainer(string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func stopContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerStop(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func restartContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerRestart(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func deleteContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerRemove(context.Background(), string(containerID), types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: true, Force: true}); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func logContainerHandler(w http.ResponseWriter, containerID []byte) {
	logs, err := docker.ContainerLogs(context.Background(), string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: false})
	if err != nil {
		errorHandler(w, err)
		return
	}

	defer logs.Close()

	if logLines, err := ioutil.ReadAll(logs); err != nil {
		errorHandler(w, err)
	} else {
		matches := splitLogs.FindAllSubmatch(logLines, -1)
		cleanLogs := make([]string, 0, len(matches))
		for _, match := range matches {
			cleanLogs = append(cleanLogs, string(match[1]))
		}

		jsonHttp.ResponseJSON(w, results{cleanLogs})
	}
}

func listContainersHandler(w http.ResponseWriter) {
	if containers, err := docker.ContainerList(context.Background(), types.ContainerListOptions{All: true}); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, results{containers})
	}
}

func readBody(body io.ReadCloser) ([]byte, error) {
	defer body.Close()
	return ioutil.ReadAll(body)
}

func getConfig(service *dockerComposeService, loggedUser *user) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	service.Labels[`owner`] = loggedUser.username

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if service.Command != `` {
		config.Cmd = strslice.StrSlice([]string{service.Command})
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    134217728,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		hostConfig.Resources.Memory = service.MemoryLimit
	}

	return &hostConfig
}

func runComposeHandler(w http.ResponseWriter, loggedUser *user, name []byte, composeFile []byte) {
	compose := dockerCompose{}

	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, err)
		return
	}

	ids := make([]string, len(compose.Services))
	for serviceName, service := range compose.Services {
		pull, err := docker.ImagePull(context.Background(), service.Image, types.ImagePullOptions{})
		defer pull.Close();
		if err != nil {
			errorHandler(w, err)
			return
		}

		id, err := docker.ContainerCreate(context.Background(), getConfig(&service, loggedUser), getHostConfig(&service), &networkConfig, string(name)+`_`+serviceName)
		if err != nil {
			errorHandler(w, err)
			return
		}

		startContainer(id.ID)
		ids = append(ids, id.ID)
	}

	jsonHttp.ResponseJSON(w, results{ids})
}

func isAuthenticated(r *http.Request) *user {
	username, password, ok := r.BasicAuth()

	if ok {
		user, ok := users[strings.ToLower(username)]

		if ok && user.password == password {
			return user
		}
	}

	return nil
}

func unauthorized(w http.ResponseWriter) {
	http.Error(w, `Authentication required`, http.StatusUnauthorized)
}

// Handler for Hello request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type, Authorization`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET, POST, DELETE`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	if r.Method == http.MethodOptions {
		w.Write(nil)
		return
	}

	urlPath := []byte(r.URL.Path)

	if containersRequest.Match(urlPath) && r.Method == http.MethodGet {
		listContainersHandler(w)
	} else if loggedUser := isAuthenticated(r); loggedUser!= nil {
		if containerRequest.Match(urlPath) && r.Method == http.MethodPost {
			if composeBody, err := readBody(r.Body); err != nil {
				errorHandler(w, err)
			} else {
				runComposeHandler(w, loggedUser, containerRequest.FindSubmatch(urlPath)[1], composeBody)
			}
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodGet {
			inspectContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
		} else if startRequest.Match(urlPath) && r.Method == http.MethodPost {
			startContainerHandler(w, startRequest.FindSubmatch(urlPath)[1])
		} else if stopRequest.Match(urlPath) && r.Method == http.MethodPost {
			stopContainerHandler(w, stopRequest.FindSubmatch(urlPath)[1])
		} else if restartRequest.Match(urlPath) && r.Method == http.MethodPost {
			restartContainerHandler(w, restartRequest.FindSubmatch(urlPath)[1])
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodDelete {
			deleteContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
		} else if logRequest.Match(urlPath) && r.Method == http.MethodGet {
			logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
		}
	} else {
		unauthorized(w)
	}
}
<commit_msg>Removing removal links<commit_after>package docker

import (
	"bufio"
	"bytes"
	"context"
	"github.com/ViBiOh/docker-deploy/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/client"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
)

const host = `DOCKER_HOST`
const version = `DOCKER_VERSION`
const configurationFile = `./users`

var commaByte = []byte(`,`)
var splitLogs = regexp.MustCompile(`.{8}(.*?)\n`)

var networkConfig = network.NetworkingConfig{
	EndpointsConfig: map[string]*network.EndpointSettings{
		`traefik`: &network.EndpointSettings{},
	},
}

var containersRequest = regexp.MustCompile(`/containers/?$`)
var containerRequest = regexp.MustCompile(`/containers/([^/]+)/?$`)
var startRequest = regexp.MustCompile(`/containers/([^/]+)/start`)
var stopRequest = regexp.MustCompile(`/containers/([^/]+)/stop`)
var restartRequest = regexp.MustCompile(`/containers/([^/]+)/restart`)
var logRequest = regexp.MustCompile(`/containers/([^/]+)/logs`)

type results struct {
	Results interface{} `json:"results"`
}

type user struct {
	username string
	password string
	role     string
}

type dockerComposeService struct {
	Image       string
	Command     string
	Environment map[string]string
	Labels      map[string]string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

var docker *client.Client
var users map[string]*user

func errorHandler(w http.ResponseWriter, err error) {
	log.Print(err)
	http.Error(w, err.Error(), http.StatusInternalServerError)
}

func init() {
	users = readConfiguration(configurationFile)

	client, err := client.NewClient(os.Getenv(host), os.Getenv(version), nil, nil)
	if err != nil {
		log.Fatal(err)
	} else {
		docker = client
	}
}

func readConfiguration(path string) map[string]*user {
	configFile, err := os.Open(path)
	defer configFile.Close()

	if err != nil {
		log.Print(err)
		return nil
	}

	users := make(map[string]*user)

	scanner := bufio.NewScanner(configFile)
	for scanner.Scan() {
		parts := bytes.Split(scanner.Bytes(), commaByte)
		user := user{string(parts[0]), string(parts[1]), string(parts[2])}

		users[strings.ToLower(user.username)] = &user
	}

	return users
}

func inspectContainerHandler(w http.ResponseWriter, containerID []byte) {
	if container, err := docker.ContainerInspect(context.Background(), string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, container)
	}
}

func startContainer(containerID string) error {
	return docker.ContainerStart(context.Background(), string(containerID), types.ContainerStartOptions{})
}

func startContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := startContainer(string(containerID)); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func stopContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerStop(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func restartContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerRestart(context.Background(), string(containerID), nil); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func deleteContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := docker.ContainerRemove(context.Background(), string(containerID), types.ContainerRemoveOptions{RemoveVolumes: true, Force: true}); err != nil {
		errorHandler(w, err)
	} else {
		w.Write(nil)
	}
}

func logContainerHandler(w http.ResponseWriter, containerID []byte) {
	logs, err := docker.ContainerLogs(context.Background(), string(containerID), types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: false})
	if err != nil {
		errorHandler(w, err)
		return
	}

	defer logs.Close()

	if logLines, err := ioutil.ReadAll(logs); err != nil {
		errorHandler(w, err)
	} else {
		matches := splitLogs.FindAllSubmatch(logLines, -1)
		cleanLogs := make([]string, 0, len(matches))
		for _, match := range matches {
			cleanLogs = append(cleanLogs, string(match[1]))
		}

		jsonHttp.ResponseJSON(w, results{cleanLogs})
	}
}

func listContainersHandler(w http.ResponseWriter) {
	if containers, err := docker.ContainerList(context.Background(), types.ContainerListOptions{All: true}); err != nil {
		errorHandler(w, err)
	} else {
		jsonHttp.ResponseJSON(w, results{containers})
	}
}

func readBody(body io.ReadCloser) ([]byte, error) {
	defer body.Close()
	return ioutil.ReadAll(body)
}

func getConfig(service *dockerComposeService, loggedUser *user) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	service.Labels[`owner`] = loggedUser.username

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if service.Command != `` {
		config.Cmd = strslice.StrSlice([]string{service.Command})
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    134217728,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		hostConfig.Resources.Memory = service.MemoryLimit
	}

	return &hostConfig
}

func runComposeHandler(w http.ResponseWriter, loggedUser *user, name []byte, composeFile []byte) {
	compose := dockerCompose{}

	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, err)
		return
	}

	ids := make([]string, len(compose.Services))
	for serviceName, service := range compose.Services {
		pull, err := docker.ImagePull(context.Background(), service.Image, types.ImagePullOptions{})
		defer pull.Close();
		if err != nil {
			errorHandler(w, err)
			return
		}

		id, err := docker.ContainerCreate(context.Background(), getConfig(&service, loggedUser), getHostConfig(&service), &networkConfig, string(name)+`_`+serviceName)
		if err != nil {
			errorHandler(w, err)
			return
		}

		startContainer(id.ID)
		ids = append(ids, id.ID)
	}

	jsonHttp.ResponseJSON(w, results{ids})
}

func isAuthenticated(r *http.Request) *user {
	username, password, ok := r.BasicAuth()

	if ok {
		user, ok := users[strings.ToLower(username)]

		if ok && user.password == password {
			return user
		}
	}

	return nil
}

func unauthorized(w http.ResponseWriter) {
	http.Error(w, `Authentication required`, http.StatusUnauthorized)
}

// Handler for Hello request. Should be use with net/http
type Handler struct {
}

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Add(`Access-Control-Allow-Origin`, `*`)
	w.Header().Add(`Access-Control-Allow-Headers`, `Content-Type, Authorization`)
	w.Header().Add(`Access-Control-Allow-Methods`, `GET, POST, DELETE`)
	w.Header().Add(`X-Content-Type-Options`, `nosniff`)

	if r.Method == http.MethodOptions {
		w.Write(nil)
		return
	}

	urlPath := []byte(r.URL.Path)

	if containersRequest.Match(urlPath) && r.Method == http.MethodGet {
		listContainersHandler(w)
	} else if loggedUser := isAuthenticated(r); loggedUser!= nil {
		if containerRequest.Match(urlPath) && r.Method == http.MethodPost {
			if composeBody, err := readBody(r.Body); err != nil {
				errorHandler(w, err)
			} else {
				runComposeHandler(w, loggedUser, containerRequest.FindSubmatch(urlPath)[1], composeBody)
			}
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodGet {
			inspectContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
		} else if startRequest.Match(urlPath) && r.Method == http.MethodPost {
			startContainerHandler(w, startRequest.FindSubmatch(urlPath)[1])
		} else if stopRequest.Match(urlPath) && r.Method == http.MethodPost {
			stopContainerHandler(w, stopRequest.FindSubmatch(urlPath)[1])
		} else if restartRequest.Match(urlPath) && r.Method == http.MethodPost {
			restartContainerHandler(w, restartRequest.FindSubmatch(urlPath)[1])
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodDelete {
			deleteContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
		} else if logRequest.Match(urlPath) && r.Method == http.MethodGet {
			logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
		}
	} else {
		unauthorized(w)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"log"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"
)

type Service struct {
	Name    string
	Image   string
	Ip      net.IP
	Ttl     int
	Aliases []string
}

func NewService() (s *Service) {
	s = &Service{Ttl: -1}
	return
}

type ServiceListProvider interface {
	AddService(string, Service)
	RemoveService(string) error
	GetService(string) (Service, error)
	GetAllServices() map[string]Service
}

type DNSServer struct {
	config   *Config
	server   *dns.Server
	mux      *dns.ServeMux
	services map[string]*Service
	lock     *sync.RWMutex
}

func NewDNSServer(c *Config) *DNSServer {
	s := &DNSServer{
		config:   c,
		services: make(map[string]*Service),
		lock:     &sync.RWMutex{},
	}

	s.mux = dns.NewServeMux()
	s.mux.HandleFunc(c.domain.String()+".", s.handleRequest)
	s.mux.HandleFunc("in-addr.arpa.", s.handleReverseRequest)
	s.mux.HandleFunc(".", s.handleForward)

	s.server = &dns.Server{Addr: c.dnsAddr, Net: "udp", Handler: s.mux}

	return s
}

func (s *DNSServer) Start() error {
	return s.server.ListenAndServe()
}

func (s *DNSServer) Stop() {
	s.server.Shutdown()
}

func (s *DNSServer) AddService(id string, service Service) {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	s.services[id] = &service

	for _, alias := range service.Aliases {
		s.mux.HandleFunc(alias+".", s.handleRequest)
	}

	if s.config.verbose {
		log.Println("Added service:", id, service)
	}
}

func (s *DNSServer) RemoveService(id string) error {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	if _, ok := s.services[id]; !ok {
		return errors.New("No such service: " + id)
	}

	for _, alias := range s.services[id].Aliases {
		s.mux.HandleRemove(alias + ".")
	}

	delete(s.services, id)

	if s.config.verbose {
		log.Println("Stopped service:", id)
	}

	return nil
}

func (s *DNSServer) GetService(id string) (Service, error) {
	defer s.lock.RUnlock()
	s.lock.RLock()

	id = s.getExpandedID(id)
	if s, ok := s.services[id]; ok {
		return *s, nil
	}
	// Check for a pa
	return *new(Service), errors.New("No such service: " + id)
}

func (s *DNSServer) GetAllServices() map[string]Service {
	defer s.lock.RUnlock()
	s.lock.RLock()

	list := make(map[string]Service, len(s.services))
	for id, service := range s.services {
		list[id] = *service
	}

	return list
}

func (s *DNSServer) listDomains(service *Service) chan string {
	c := make(chan string)

	go func() {

		if service.Image == "" {
			c <- service.Name + "." + s.config.domain.String() + "."
		} else {
			domain := service.Image + "." + s.config.domain.String() + "."

			c <- domain
			c <- service.Name + "." + domain
		}

		for _, alias := range service.Aliases {
			c <- alias + "."
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) handleForward(w dns.ResponseWriter, r *dns.Msg) {
	// Otherwise just forward the request to another server
	c := new(dns.Client)
	if in, _, err := c.Exchange(r, s.config.nameserver); err != nil {
		log.Print(err)

		m := new(dns.Msg)
		m.SetReply(r)
		m.Ns = s.createSOA()
		m.SetRcode(r, dns.RcodeRefused) // REFUSED

		w.WriteMsg(m)
	} else {
		w.WriteMsg(in)
	}
}

func (s *DNSServer) makeServiceA(n string, service *Service) dns.RR {
	rr := new(dns.A)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.A = service.Ip

	return rr
}

func (s *DNSServer) makeServiceMX(n string, service *Service) dns.RR {
	rr := new(dns.MX)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeMX,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.Mx = n

	return rr
}

func (s *DNSServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	// respond to SOA requests
	if r.Question[0].Qtype == dns.TypeSOA {
		m.Answer = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryServices(query) {
		var rr dns.RR
		switch r.Question[0].Qtype {
		case dns.TypeA:
			rr = s.makeServiceA(r.Question[0].Name, service)
		case dns.TypeMX:
			rr = s.makeServiceMX(r.Question[0].Name, service)
		default:
			// this query type isn't supported, but we do have
			// a record with this name. Per RFC 4074 sec. 3, we
			// immediately return an empty NOERROR reply.
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		m.Answer = append(m.Answer, rr)
	}

	// We didn't find a record corresponding to the query
	if len(m.Answer) == 0 {
		m.Ns = s.createSOA()
		m.SetRcode(r, dns.RcodeNameError) // NXDOMAIN
	}

	w.WriteMsg(m)
}

func (s *DNSServer) handleReverseRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryIp(query) {
		if r.Question[0].Qtype != dns.TypePTR {
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		var ttl int
		if service.Ttl != -1 {
			ttl = service.Ttl
		} else {
			ttl = s.config.ttl
		}

		for domain := range s.listDomains(service) {
			rr := new(dns.PTR)
			rr.Hdr = dns.RR_Header{
				Name:   r.Question[0].Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl),
			}
			rr.Ptr = domain

			m.Answer = append(m.Answer, rr)
		}
	}

	if len(m.Answer) != 0 {
		w.WriteMsg(m)
	} else {
		// We didn't find a record corresponding to the query,
		// try forwarding
		s.handleForward(w, r)
	}
}

func (s *DNSServer) queryIp(query string) chan *Service {
	c := make(chan *Service, 3)
	reversedIp := strings.TrimSuffix(query, ".in-addr.arpa")
	ip := strings.Join(reverse(strings.Split(reversedIp, ".")), ".")

	go func() {
		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			if service.Ip.String() == ip {
				c <- service
			}
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) queryServices(query string) chan *Service {
	c := make(chan *Service, 3)

	go func() {
		query := strings.Split(strings.ToLower(query), ".")

		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			// create the name for this service, skip empty strings
			test := []string{}
			// todo: add some cache to avoid calculating this every time
			if len(service.Name) > 0 {
				test = append(test, strings.Split(service.Name, ".")...)
			}

			if len(service.Image) > 0 {
				test = append(test, strings.Split(service.Image, ".")...)
			}

			test = append(test, s.config.domain...)

			if isPrefixQuery(query, test) {
				c <- service
			}

			// check aliases
			for _, alias := range service.Aliases {
				if isPrefixQuery(query, strings.Split(alias, ".")) {
					c <- service
				}
			}
		}

		close(c)

	}()

	return c

}

// Checks for a partial match for container SHA and outputs it if found.
func (s *DNSServer) getExpandedID(in string) (out string) {
	out = in

	// Hard to make a judgement on small image names.
	if len(in) < 4 {
		return
	}

	if isHex, _ := regexp.MatchString("^[0-9a-f]+$", in); !isHex {
		return
	}

	for id := range s.services {
		if len(id) == 64 {
			if isHex, _ := regexp.MatchString("^[0-9a-f]+$", id); isHex {
				if strings.HasPrefix(id, in) {
					out = id
					return
				}
			}
		}
	}
	return
}

// Ttl is used from config so that not-found result responses are not cached
// for a long time. The other defaults left as is(skydns source) because they
// do not have an use case in this situation.
func (s *DNSServer) createSOA() []dns.RR {
	dom := dns.Fqdn(s.config.domain.String() + ".")
	soa := &dns.SOA{
		Hdr: dns.RR_Header{
			Name:   dom,
			Rrtype: dns.TypeSOA,
			Class:  dns.ClassINET,
			Ttl:    uint32(s.config.ttl)},
		Ns:      "dnsdock." + dom,
		Mbox:    "dnsdock.dnsdock." + dom,
		Serial:  uint32(time.Now().Truncate(time.Hour).Unix()),
		Refresh: 28800,
		Retry:   7200,
		Expire:  604800,
		Minttl:  uint32(s.config.ttl),
	}
	return []dns.RR{soa}
}

// isPrefixQuery is used to determine whether "query" is a potential prefix
// query for "name". It allows for wildcards (*) in the query. However is makes
// one exception to accomodate the desired behavior we wish from dnsdock,
// namely, the query may be longer than "name" and still be a valid prefix
// query for "name".
// Examples:
//   foo.bar.baz.qux is a valid query for bar.baz.qux (longer prefix is okay)
//   foo.*.baz.qux   is a valid query for bar.baz.qux (wildcards okay)
//   *.baz.qux       is a valid query for baz.baz.qux (wildcard prefix okay)
func isPrefixQuery(query, name []string) bool {
	for i, j := len(query)-1, len(name)-1; i >= 0 && j >= 0; i, j = i-1, j-1 {
		if query[i] != name[j] && query[i] != "*" {
			return false
		}
	}
	return true
}

func reverse(input []string) []string {
	if len(input) == 0 {
		return input
	}

	return append(reverse(input[1:]), input[0])
}
<commit_msg>Support for Containers w/ Uppercase Characters<commit_after>package main

import (
	"errors"
	"log"
	"net"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"
)

type Service struct {
	Name    string
	Image   string
	Ip      net.IP
	Ttl     int
	Aliases []string
}

func NewService() (s *Service) {
	s = &Service{Ttl: -1}
	return
}

type ServiceListProvider interface {
	AddService(string, Service)
	RemoveService(string) error
	GetService(string) (Service, error)
	GetAllServices() map[string]Service
}

type DNSServer struct {
	config   *Config
	server   *dns.Server
	mux      *dns.ServeMux
	services map[string]*Service
	lock     *sync.RWMutex
}

func NewDNSServer(c *Config) *DNSServer {
	s := &DNSServer{
		config:   c,
		services: make(map[string]*Service),
		lock:     &sync.RWMutex{},
	}

	s.mux = dns.NewServeMux()
	s.mux.HandleFunc(c.domain.String()+".", s.handleRequest)
	s.mux.HandleFunc("in-addr.arpa.", s.handleReverseRequest)
	s.mux.HandleFunc(".", s.handleForward)

	s.server = &dns.Server{Addr: c.dnsAddr, Net: "udp", Handler: s.mux}

	return s
}

func (s *DNSServer) Start() error {
	return s.server.ListenAndServe()
}

func (s *DNSServer) Stop() {
	s.server.Shutdown()
}

func (s *DNSServer) AddService(id string, service Service) {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	s.services[id] = &service

	for _, alias := range service.Aliases {
		s.mux.HandleFunc(alias+".", s.handleRequest)
	}

	if s.config.verbose {
		log.Println("Added service:", id, service)
	}
}

func (s *DNSServer) RemoveService(id string) error {
	defer s.lock.Unlock()
	s.lock.Lock()

	id = s.getExpandedID(id)
	if _, ok := s.services[id]; !ok {
		return errors.New("No such service: " + id)
	}

	for _, alias := range s.services[id].Aliases {
		s.mux.HandleRemove(alias + ".")
	}

	delete(s.services, id)

	if s.config.verbose {
		log.Println("Stopped service:", id)
	}

	return nil
}

func (s *DNSServer) GetService(id string) (Service, error) {
	defer s.lock.RUnlock()
	s.lock.RLock()

	id = s.getExpandedID(id)
	if s, ok := s.services[id]; ok {
		return *s, nil
	}
	// Check for a pa
	return *new(Service), errors.New("No such service: " + id)
}

func (s *DNSServer) GetAllServices() map[string]Service {
	defer s.lock.RUnlock()
	s.lock.RLock()

	list := make(map[string]Service, len(s.services))
	for id, service := range s.services {
		list[id] = *service
	}

	return list
}

func (s *DNSServer) listDomains(service *Service) chan string {
	c := make(chan string)

	go func() {

		if service.Image == "" {
			c <- service.Name + "." + s.config.domain.String() + "."
		} else {
			domain := service.Image + "." + s.config.domain.String() + "."

			c <- domain
			c <- service.Name + "." + domain
		}

		for _, alias := range service.Aliases {
			c <- alias + "."
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) handleForward(w dns.ResponseWriter, r *dns.Msg) {
	// Otherwise just forward the request to another server
	c := new(dns.Client)
	if in, _, err := c.Exchange(r, s.config.nameserver); err != nil {
		log.Print(err)

		m := new(dns.Msg)
		m.SetReply(r)
		m.Ns = s.createSOA()
		m.SetRcode(r, dns.RcodeRefused) // REFUSED

		w.WriteMsg(m)
	} else {
		w.WriteMsg(in)
	}
}

func (s *DNSServer) makeServiceA(n string, service *Service) dns.RR {
	rr := new(dns.A)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeA,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.A = service.Ip

	return rr
}

func (s *DNSServer) makeServiceMX(n string, service *Service) dns.RR {
	rr := new(dns.MX)

	var ttl int
	if service.Ttl != -1 {
		ttl = service.Ttl
	} else {
		ttl = s.config.ttl
	}

	rr.Hdr = dns.RR_Header{
		Name:   n,
		Rrtype: dns.TypeMX,
		Class:  dns.ClassINET,
		Ttl:    uint32(ttl),
	}

	rr.Mx = n

	return rr
}

func (s *DNSServer) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	// respond to SOA requests
	if r.Question[0].Qtype == dns.TypeSOA {
		m.Answer = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryServices(query) {
		var rr dns.RR
		switch r.Question[0].Qtype {
		case dns.TypeA:
			rr = s.makeServiceA(r.Question[0].Name, service)
		case dns.TypeMX:
			rr = s.makeServiceMX(r.Question[0].Name, service)
		default:
			// this query type isn't supported, but we do have
			// a record with this name. Per RFC 4074 sec. 3, we
			// immediately return an empty NOERROR reply.
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		m.Answer = append(m.Answer, rr)
	}

	// We didn't find a record corresponding to the query
	if len(m.Answer) == 0 {
		m.Ns = s.createSOA()
		m.SetRcode(r, dns.RcodeNameError) // NXDOMAIN
	}

	w.WriteMsg(m)
}

func (s *DNSServer) handleReverseRequest(w dns.ResponseWriter, r *dns.Msg) {
	m := new(dns.Msg)
	m.SetReply(r)

	// Send empty response for empty requests
	if len(r.Question) == 0 {
		m.Ns = s.createSOA()
		w.WriteMsg(m)
		return
	}

	m.Answer = make([]dns.RR, 0, 2)
	query := r.Question[0].Name

	// trim off any trailing dot
	if query[len(query)-1] == '.' {
		query = query[:len(query)-1]
	}

	for service := range s.queryIp(query) {
		if r.Question[0].Qtype != dns.TypePTR {
			m.Ns = s.createSOA()
			w.WriteMsg(m)
			return
		}

		var ttl int
		if service.Ttl != -1 {
			ttl = service.Ttl
		} else {
			ttl = s.config.ttl
		}

		for domain := range s.listDomains(service) {
			rr := new(dns.PTR)
			rr.Hdr = dns.RR_Header{
				Name:   r.Question[0].Name,
				Rrtype: dns.TypePTR,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl),
			}
			rr.Ptr = domain

			m.Answer = append(m.Answer, rr)
		}
	}

	if len(m.Answer) != 0 {
		w.WriteMsg(m)
	} else {
		// We didn't find a record corresponding to the query,
		// try forwarding
		s.handleForward(w, r)
	}
}

func (s *DNSServer) queryIp(query string) chan *Service {
	c := make(chan *Service, 3)
	reversedIp := strings.TrimSuffix(query, ".in-addr.arpa")
	ip := strings.Join(reverse(strings.Split(reversedIp, ".")), ".")

	go func() {
		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			if service.Ip.String() == ip {
				c <- service
			}
		}

		close(c)
	}()

	return c
}

func (s *DNSServer) queryServices(query string) chan *Service {
	c := make(chan *Service, 3)

	go func() {
		query := strings.Split(strings.ToLower(query), ".")

		defer s.lock.RUnlock()
		s.lock.RLock()

		for _, service := range s.services {
			// create the name for this service, skip empty strings
			test := []string{}
			// todo: add some cache to avoid calculating this every time
			if len(service.Name) > 0 {
				test = append(test, strings.Split(strings.ToLower(service.Name), ".")...)
			}

			if len(service.Image) > 0 {
				test = append(test, strings.Split(service.Image, ".")...)
			}

			test = append(test, s.config.domain...)

			if isPrefixQuery(query, test) {
				c <- service
			}

			// check aliases
			for _, alias := range service.Aliases {
				if isPrefixQuery(query, strings.Split(alias, ".")) {
					c <- service
				}
			}
		}

		close(c)

	}()

	return c

}

// Checks for a partial match for container SHA and outputs it if found.
func (s *DNSServer) getExpandedID(in string) (out string) {
	out = in

	// Hard to make a judgement on small image names.
	if len(in) < 4 {
		return
	}

	if isHex, _ := regexp.MatchString("^[0-9a-f]+$", in); !isHex {
		return
	}

	for id := range s.services {
		if len(id) == 64 {
			if isHex, _ := regexp.MatchString("^[0-9a-f]+$", id); isHex {
				if strings.HasPrefix(id, in) {
					out = id
					return
				}
			}
		}
	}
	return
}

// Ttl is used from config so that not-found result responses are not cached
// for a long time. The other defaults left as is(skydns source) because they
// do not have an use case in this situation.
func (s *DNSServer) createSOA() []dns.RR {
	dom := dns.Fqdn(s.config.domain.String() + ".")
	soa := &dns.SOA{
		Hdr: dns.RR_Header{
			Name:   dom,
			Rrtype: dns.TypeSOA,
			Class:  dns.ClassINET,
			Ttl:    uint32(s.config.ttl)},
		Ns:      "dnsdock." + dom,
		Mbox:    "dnsdock.dnsdock." + dom,
		Serial:  uint32(time.Now().Truncate(time.Hour).Unix()),
		Refresh: 28800,
		Retry:   7200,
		Expire:  604800,
		Minttl:  uint32(s.config.ttl),
	}
	return []dns.RR{soa}
}

// isPrefixQuery is used to determine whether "query" is a potential prefix
// query for "name". It allows for wildcards (*) in the query. However is makes
// one exception to accomodate the desired behavior we wish from dnsdock,
// namely, the query may be longer than "name" and still be a valid prefix
// query for "name".
// Examples:
//   foo.bar.baz.qux is a valid query for bar.baz.qux (longer prefix is okay)
//   foo.*.baz.qux   is a valid query for bar.baz.qux (wildcards okay)
//   *.baz.qux       is a valid query for baz.baz.qux (wildcard prefix okay)
func isPrefixQuery(query, name []string) bool {
	for i, j := len(query)-1, len(name)-1; i >= 0 && j >= 0; i, j = i-1, j-1 {
		if query[i] != name[j] && query[i] != "*" {
			return false
		}
	}
	return true
}

func reverse(input []string) []string {
	if len(input) == 0 {
		return input
	}

	return append(reverse(input[1:]), input[0])
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/digitalocean/go-metadata"
	"github.com/digitalocean/godo"
)

const (
	StorageActionRetryCount             = 3
	StorageActionRetryInterval          = 1000 * time.Millisecond
	StorageActionCompletionPollCount    = 60
	StorageActionCompletionPollInterval = 500 * time.Millisecond
)

type DoFacade struct {
	metadataClient *metadata.Client
	apiClient      *godo.Client
}

func NewDoFacade(metadataClient *metadata.Client, apiClient *godo.Client) *DoFacade {
	return &DoFacade{
		metadataClient: metadataClient,
		apiClient:      apiClient,
	}
}

func (s DoFacade) GetLocalRegion() (string, error) {
	return s.metadataClient.Region()
}

func (s DoFacade) GetLocalDropletID() (int, error) {
	return s.metadataClient.DropletID()
}

func (s DoFacade) GetVolume(volumeID string) (*godo.Volume, error) {
	doVolume, _, err := s.apiClient.Storage.GetVolume(volumeID)
	return doVolume, err
}

func (s DoFacade) GetVolumeByRegionAndName(region string, name string) *godo.Volume {
	doVolumes, _, _ := s.apiClient.Storage.ListVolumes(&godo.ListOptions{PerPage: 1000000})

	for _, doVolume := range doVolumes {
		if doVolume.Region.Slug == region && doVolume.Name == name {
			return &doVolume
		}
	}
	return nil
}

func (s DoFacade) IsVolumeAttachedToDroplet(volumeID string, dropletID int) bool {
	doVolume, _, err := s.apiClient.Storage.GetVolume(volumeID)

	if err != nil {
		logrus.Errorf("failed to get the volume: %v", err)
		return false
	}

	for _, attachedDropletID := range doVolume.DropletIDs {
		if attachedDropletID == dropletID {
			return true
		}
	}
	return false
}

func (s DoFacade) DetachVolumeFromAllDroplets(volumeID string) error {
	logrus.Infof("detaching the volume '%v' from all droplets", volumeID)

	attachedDropletIDs := s.getAttachedDroplets(volumeID)
	for _, attachedDropletID := range attachedDropletIDs {
		derr := s.DetachVolumeFromDroplet(volumeID, attachedDropletID)
		if derr != nil {
			return derr
		}
	}
	return nil
}

func (s DoFacade) DetachVolumeFromDroplet(volumeID string, dropletID int) error {
	logrus.Infof("detaching the volume from the droplet %v", dropletID)

	var lastErr error

	for i := 1; i <= StorageActionRetryCount; i++ {
		action, _, derr := s.apiClient.StorageActions.Detach(volumeID, dropletID)
		if derr != nil {
			logrus.Errorf("failed to detach the volume: %v", lastErr)
			time.Sleep(StorageActionRetryInterval)
			lastErr = derr
		} else {
			lastErr = s.waitForVolumeActionToComplete(volumeID, action.ID)
			break
		}
	}

	return lastErr
}

func (s DoFacade) AttachVolumeToDroplet(volumeID string, dropletID int) error {
	logrus.Infof("detaching the volume '%v' from the droplet %v", dropletID)

	var lastErr error

	for i := 1; i <= StorageActionRetryCount; i++ {
		action, _, aerr := s.apiClient.StorageActions.Attach(volumeID, dropletID)
		if aerr != nil {
			logrus.Errorf("failed to attach the volume: %v", aerr)
			time.Sleep(StorageActionRetryInterval)
			lastErr = aerr
		} else {
			lastErr = s.waitForVolumeActionToComplete(volumeID, action.ID)
			break
		}
	}

	return lastErr
}

func (s DoFacade) getAttachedDroplets(volumeID string) []int {
	doVolume, _, err := s.apiClient.Storage.GetVolume(volumeID)
	if err != nil {
		logrus.Errorf("Error getting the volume: %v", err.Error())
		return []int{}
	}
	return doVolume.DropletIDs
}

func (s DoFacade) waitForVolumeActionToComplete(volumeID string, actionID int) error {
	logrus.Infof("waiting for the storage action %v to complete", actionID)

	lastStatus := "n/a"

	for i := 1; i <= StorageActionCompletionPollCount; i++ {
		action, _, aerr := s.apiClient.StorageActions.Get(volumeID, actionID)
		if aerr == nil {
			lastStatus = action.Status
			if action.Status == "completed" || action.Status == "errored" {
				break
			}
		} else {
			logrus.Errorf("failed to query the storage action: %v", aerr)
		}
		time.Sleep(StorageActionCompletionPollInterval)
	}

	if lastStatus == "completed" {
		logrus.Info("the action completed")
		return nil
	}

	logrus.Errorf("the action did not complete but ended with status '%v'", lastStatus)
	return fmt.Errorf("the action did not complete but ended with status '%v'", lastStatus)
}
<commit_msg>Fix volume lookup (resolves #6)<commit_after>package main

import (
	"fmt"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/digitalocean/go-metadata"
	"github.com/digitalocean/godo"
)

const (
	StorageActionRetryCount             = 3
	StorageActionRetryInterval          = 1000 * time.Millisecond
	StorageActionCompletionPollCount    = 60
	StorageActionCompletionPollInterval = 500 * time.Millisecond
	MaxResultsPerPage                   = 200
)

type DoFacade struct {
	metadataClient *metadata.Client
	apiClient      *godo.Client
}

func NewDoFacade(metadataClient *metadata.Client, apiClient *godo.Client) *DoFacade {
	return &DoFacade{
		metadataClient: metadataClient,
		apiClient:      apiClient,
	}
}

func (s DoFacade) GetLocalRegion() (string, error) {
	return s.metadataClient.Region()
}

func (s DoFacade) GetLocalDropletID() (int, error) {
	return s.metadataClient.DropletID()
}

func (s DoFacade) GetVolume(volumeID string) (*godo.Volume, error) {
	doVolume, _, err := s.apiClient.Storage.GetVolume(volumeID)
	return doVolume, err
}

func (s DoFacade) GetVolumeByRegionAndName(region string, name string) *godo.Volume {
	doVolumes, _, err := s.apiClient.Storage.ListVolumes(&godo.ListOptions{Page: 1, PerPage: MaxResultsPerPage})

	if err != nil {
		logrus.Errorf("failed to get the volume by region and name: %v", err)
		return nil
	}

	for i := range doVolumes {
		if doVolumes[i].Region.Slug == region && doVolumes[i].Name == name {
			return &doVolumes[i]
		}
	}
	return nil
}

func (s DoFacade) IsVolumeAttachedToDroplet(volumeID string, dropletID int) bool {
	doVolume, _, err := s.apiClient.Storage.GetVolume(volumeID)

	if err != nil {
		logrus.Errorf("failed to get the volume: %v", err)
		return false
	}

	for _, attachedDropletID := range doVolume.DropletIDs {
		if attachedDropletID == dropletID {
			return true
		}
	}
	return false
}

func (s DoFacade) DetachVolumeFromAllDroplets(volumeID string) error {
	logrus.Infof("detaching the volume '%v' from all droplets", volumeID)

	attachedDropletIDs := s.getAttachedDroplets(volumeID)
	for _, attachedDropletID := range attachedDropletIDs {
		derr := s.DetachVolumeFromDroplet(volumeID, attachedDropletID)
		if derr != nil {
			return derr
		}
	}
	return nil
}

func (s DoFacade) DetachVolumeFromDroplet(volumeID string, dropletID int) error {
	logrus.Infof("detaching the volume from the droplet %v", dropletID)

	var lastErr error

	for i := 1; i <= StorageActionRetryCount; i++ {
		action, _, derr := s.apiClient.StorageActions.Detach(volumeID, dropletID)
		if derr != nil {
			logrus.Errorf("failed to detach the volume: %v", lastErr)
			time.Sleep(StorageActionRetryInterval)
			lastErr = derr
		} else {
			lastErr = s.waitForVolumeActionToComplete(volumeID, action.ID)
			break
		}
	}

	return lastErr
}

func (s DoFacade) AttachVolumeToDroplet(volumeID string, dropletID int) error {
	logrus.Infof("detaching the volume '%v' from the droplet %v", dropletID)

	var lastErr error

	for i := 1; i <= StorageActionRetryCount; i++ {
		action, _, aerr := s.apiClient.StorageActions.Attach(volumeID, dropletID)
		if aerr != nil {
			logrus.Errorf("failed to attach the volume: %v", aerr)
			time.Sleep(StorageActionRetryInterval)
			lastErr = aerr
		} else {
			lastErr = s.waitForVolumeActionToComplete(volumeID, action.ID)
			break
		}
	}

	return lastErr
}

func (s DoFacade) getAttachedDroplets(volumeID string) []int {
	doVolume, _, err := s.apiClient.Storage.GetVolume(volumeID)
	if err != nil {
		logrus.Errorf("Error getting the volume: %v", err.Error())
		return []int{}
	}
	return doVolume.DropletIDs
}

func (s DoFacade) waitForVolumeActionToComplete(volumeID string, actionID int) error {
	logrus.Infof("waiting for the storage action %v to complete", actionID)

	lastStatus := "n/a"

	for i := 1; i <= StorageActionCompletionPollCount; i++ {
		action, _, aerr := s.apiClient.StorageActions.Get(volumeID, actionID)
		if aerr == nil {
			lastStatus = action.Status
			if action.Status == "completed" || action.Status == "errored" {
				break
			}
		} else {
			logrus.Errorf("failed to query the storage action: %v", aerr)
		}
		time.Sleep(StorageActionCompletionPollInterval)
	}

	if lastStatus == "completed" {
		logrus.Info("the action completed")
		return nil
	}

	logrus.Errorf("the action did not complete but ended with status '%v'", lastStatus)
	return fmt.Errorf("the action did not complete but ended with status '%v'", lastStatus)
}
<|endoftext|>