<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 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 {
		if _, err := docker.ImagePull(context.Background(), service.Image, types.ImagePullOptions{}); 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`)
	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 logRequest.Match(urlPath) && r.Method == http.MethodGet {
			logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
		}
	} else {
		unauthorized(w)
	}
}
<commit_msg>Adding close of pull output<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 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`)
	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 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/filters"
	"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 stopContainer(containerID string) error {
	return docker.ContainerStop(context.Background(), , nil)
}

func stopContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := stopContainer(string(containerID)); 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 listContainers(loggedUser *user) ([]types.Container, error) {
	options := types.ContainerListOptions{All: true}

	if loggedUser != nil {
		args, err := filters.ParseFlag(`label=owner=`+loggedUser.username, filters.NewArgs())
		if err != nil {
			return nil, err
		}
		options.Filters = args
	}

	return docker.ContainerList(context.Background(), options)
}

func listContainersHandler(w http.ResponseWriter) {
	if containers, err := listContainers(nil); 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
	}

	ownerContainers, err := listContainers(loggedUser)
	if err != nil {
		errorHandler(w, err)
		return
	}
	for container := range ownerContainers {
		stopContainer(container.ID)
	}

	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>Update docker.go<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/filters"
	"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 stopContainer(containerID string) error {
	return docker.ContainerStop(context.Background(), containerID, nil)
}

func stopContainerHandler(w http.ResponseWriter, containerID []byte) {
	if err := stopContainer(string(containerID)); 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 listContainers(loggedUser *user) ([]types.Container, error) {
	options := types.ContainerListOptions{All: true}

	if loggedUser != nil {
		args, err := filters.ParseFlag(`label=owner=`+loggedUser.username, filters.NewArgs())
		if err != nil {
			return nil, err
		}
		options.Filters = args
	}

	return docker.ContainerList(context.Background(), options)
}

func listContainersHandler(w http.ResponseWriter) {
	if containers, err := listContainers(nil); 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
	}

	ownerContainers, err := listContainers(loggedUser)
	if err != nil {
		errorHandler(w, err)
		return
	}
	for container := range ownerContainers {
		stopContainer(container.ID)
	}

	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 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/filters"
	"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`
const admin = `admin`
const ownerLabel = `owner`

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 isAllowed(loggedUser *user, containerID string) (bool, error) {
	if loggedUser.role != admin {
		container, err := inspectContainer(string(containerID))
		if err != nil {
			return false, err
		}

		owner, ok := container.Config.Labels[ownerLabel]
		if !ok || owner != loggedUser.username {
			return false, nil
		}
	}

	return true, nil
}

func listContainers(loggedUser *user) ([]types.Container, error) {
	options := types.ContainerListOptions{All: true}

	if loggedUser != nil {
		args, err := filters.ParseFlag(`label=`+ownerLabel+`=`+loggedUser.username, filters.NewArgs())
		if err != nil {
			return nil, err
		}
		options.Filters = args
	}

	return docker.ContainerList(context.Background(), options)
}

func inspectContainer(containerID string) (types.ContainerJSON, error) {
	return docker.ContainerInspect(context.Background(), containerID)
}

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

func stopContainer(containerID string) error {
	return docker.ContainerStop(context.Background(), containerID, nil)
}

func restartContainer(containerID string) error {
	return docker.ContainerRestart(context.Background(), containerID, nil)
}

func rmContainer(containerID string) error {
	return docker.ContainerRemove(context.Background(), containerID, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
}

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

func basicActionHandler(w http.ResponseWriter, loggedUser *user, containerID []byte, handle func(string) error) {
	id := string(containerID)

	allowed, err := isAllowed(loggedUser, id)
	if !allowed {
		forbidden(w)
	} else if err != nil {
		errorHandler(w, err)
	} else {
		if err = handle(id); 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 := listContainers(nil); 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[ownerLabel] = 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
	}

	ownerContainers, err := listContainers(loggedUser)
	if err != nil {
		errorHandler(w, err)
		return
	}
	for _, container := range ownerContainers {
		stopContainer(container.ID)
	}

	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)
}

func forbidden(w http.ResponseWriter) {
	http.Error(w, `Forbidden`, http.StatusForbidden)
}

// 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 {
			basicActionHandler(w, loggedUser, startRequest.FindSubmatch(urlPath)[1], startContainer)
		} else if stopRequest.Match(urlPath) && r.Method == http.MethodPost {
			basicActionHandler(w, loggedUser, stopRequest.FindSubmatch(urlPath)[1], stopContainer)
		} else if restartRequest.Match(urlPath) && r.Method == http.MethodPost {
			basicActionHandler(w, loggedUser, restartRequest.FindSubmatch(urlPath)[1], restartContainer)
		} else if containerRequest.Match(urlPath) && r.Method == http.MethodDelete {
			basicActionHandler(w, loggedUser, containerRequest.FindSubmatch(urlPath)[1], rmContainer)
		} else if logRequest.Match(urlPath) && r.Method == http.MethodGet {
			logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
		}
	} else {
		unauthorized(w)
	}
}
<commit_msg>Adding auth for listing<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/filters"
	"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`
const admin = `admin`
const ownerLabel = `owner`
const appLabel = `app`

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 isAllowed(loggedUser *user, containerID string) (bool, error) {
	if loggedUser.role != admin {
		container, err := inspectContainer(string(containerID))
		if err != nil {
			return false, err
		}

		owner, ok := container.Config.Labels[ownerLabel]
		if !ok || owner != loggedUser.username {
			return false, nil
		}
	}

	return true, nil
}

func listContainers(loggedUser *user, appName *string) ([]types.Container, error) {
	options := types.ContainerListOptions{All: true}

	options.Filters = filters.NewArgs()

	if loggedUser != nil || loggedUser.role != admin {
		_, err := filters.ParseFlag(`label=`+ownerLabel+`=`+loggedUser.username, options.Filters)
		if err != nil {
			return nil, err
		}
	} else if *appName != `` {
		_, err := filters.ParseFlag(`label=`+appLabel+`=`+*appName, options.Filters)
		if err != nil {
			return nil, err
		}
	}

	return docker.ContainerList(context.Background(), options)
}

func inspectContainer(containerID string) (types.ContainerJSON, error) {
	return docker.ContainerInspect(context.Background(), containerID)
}

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

func stopContainer(containerID string) error {
	return docker.ContainerStop(context.Background(), containerID, nil)
}

func restartContainer(containerID string) error {
	return docker.ContainerRestart(context.Background(), containerID, nil)
}

func rmContainer(containerID string) error {
	return docker.ContainerRemove(context.Background(), containerID, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
}

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

func basicActionHandler(w http.ResponseWriter, loggedUser *user, containerID []byte, handle func(string) error) {
	id := string(containerID)

	allowed, err := isAllowed(loggedUser, id)
	if !allowed {
		forbidden(w)
	} else if err != nil {
		errorHandler(w, err)
	} else {
		if err = handle(id); 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, loggerUser *user) {
	if containers, err := listContainers(loggerUser, nil); 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, appName string) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	service.Labels[ownerLabel] = loggedUser.username
	service.Labels[appLabel] = appName

	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 createAppHandler(w http.ResponseWriter, loggedUser *user, appName []byte, composeFile []byte) {
	compose := dockerCompose{}

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

	appNameStr := string(appName)
	ownerContainers, err := listContainers(loggedUser, &appNameStr)
	if err != nil {
		errorHandler(w, err)
		return
	}
	for _, container := range ownerContainers {
		stopContainer(container.ID)
	}

	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, appNameStr), getHostConfig(&service), &networkConfig, appNameStr+`_`+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)
}

func forbidden(w http.ResponseWriter) {
	http.Error(w, `Forbidden`, http.StatusForbidden)
}

// 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)

	loggedUser := isAuthenticated(r)
	if loggedUser == nil {
		unauthorized(w)
		return
	}

	if containersRequest.Match(urlPath) && r.Method == http.MethodGet {
		listContainersHandler(w, loggedUser)
	} else if containerRequest.Match(urlPath) && r.Method == http.MethodGet {
		inspectContainerHandler(w, containerRequest.FindSubmatch(urlPath)[1])
	} else if startRequest.Match(urlPath) && r.Method == http.MethodPost {
		basicActionHandler(w, loggedUser, startRequest.FindSubmatch(urlPath)[1], startContainer)
	} else if stopRequest.Match(urlPath) && r.Method == http.MethodPost {
		basicActionHandler(w, loggedUser, stopRequest.FindSubmatch(urlPath)[1], stopContainer)
	} else if restartRequest.Match(urlPath) && r.Method == http.MethodPost {
		basicActionHandler(w, loggedUser, restartRequest.FindSubmatch(urlPath)[1], restartContainer)
	} else if containerRequest.Match(urlPath) && r.Method == http.MethodDelete {
		basicActionHandler(w, loggedUser, containerRequest.FindSubmatch(urlPath)[1], rmContainer)
	} else if logRequest.Match(urlPath) && r.Method == http.MethodGet {
		logContainerHandler(w, logRequest.FindSubmatch(urlPath)[1])
	} else if containerRequest.Match(urlPath) && r.Method == http.MethodPost {
		if composeBody, err := readBody(r.Body); err != nil {
			errorHandler(w, err)
		} else {
			createAppHandler(w, loggedUser, containerRequest.FindSubmatch(urlPath)[1], composeBody)
		}
	}
}
<|endoftext|>


<commit_before>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"flag"
	"time"

	log "github.com/Sirupsen/logrus"
)

var argAPIServerHostPort string
var argVulcandURLString string
var argResyncDuration time.Duration
var argVulcandTimeout time.Duration

func init() {
	flag.StringVar(&argAPIServerHostPort, "apiserver", "", "Kubernetes apiserver host:port")
	flag.StringVar(&argVulcandURLString, "vulcand", "", "Vulcand Admin URL")
	flag.DurationVar(&argResyncDuration, "resync", 30 * time.Minute, "Resync period")
	flag.DurationVar(&argVulcandTimeout, "vulcand-timeout", 10 * time.Second, "Vulcand update timeout")

	flag.Parse()

	if argAPIServerHostPort == "" || argVulcandURLString == "" {
		log.Fatal("Missing required properties. Usage: kubernetes-vulcand-router -apiserver '[kubernetes-server]' -vulcand '[vulcand-address]'")
	}

	// Only log the warning severity or above.
	log.SetLevel(log.DebugLevel)
}

func main() {
	relay, err := NewRelay(argAPIServerHostPort, argVulcandURLString, argResyncDuration, argVulcandTimeout)
	if err != nil {
		log.WithField("error", err).Fatal("Unable to create Kubernetes Vulcand Router relay")
	}

	if err = relay.Start(); err != nil {
		log.WithField("error", err).Fatal("Unable to start Kubernetes Vulcand Router relay")
	}
	defer relay.Stop()

	select {}
	// time.Sleep(10 * time.Second)
}
<commit_msg>Remove dead code.<commit_after>/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"flag"
	"time"

	log "github.com/Sirupsen/logrus"
)

var argAPIServerHostPort string
var argVulcandURLString string
var argResyncDuration time.Duration
var argVulcandTimeout time.Duration

func init() {
	flag.StringVar(&argAPIServerHostPort, "apiserver", "", "Kubernetes apiserver host:port")
	flag.StringVar(&argVulcandURLString, "vulcand", "", "Vulcand Admin URL")
	flag.DurationVar(&argResyncDuration, "resync", 30 * time.Minute, "Resync period")
	flag.DurationVar(&argVulcandTimeout, "vulcand-timeout", 10 * time.Second, "Vulcand update timeout")

	flag.Parse()

	if argAPIServerHostPort == "" || argVulcandURLString == "" {
		log.Fatal("Missing required properties. Usage: kubernetes-vulcand-router -apiserver '[kubernetes-server]' -vulcand '[vulcand-address]'")
	}

	// Only log the warning severity or above.
	log.SetLevel(log.DebugLevel)
}

func main() {
	relay, err := NewRelay(argAPIServerHostPort, argVulcandURLString, argResyncDuration, argVulcandTimeout)
	if err != nil {
		log.WithField("error", err).Fatal("Unable to create Kubernetes Vulcand Router relay")
	}

	if err = relay.Start(); err != nil {
		log.WithField("error", err).Fatal("Unable to start Kubernetes Vulcand Router relay")
	}
	defer relay.Stop()

	select {}
}
<|endoftext|>


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

package environment

import (
	"os"
	"github.com/jkellerer/jenkins-client-launcher/launcher/util"
	"path/filepath"
	"time"
)

// Max time to persist entries inside the temporary folders.
var maxTTLInTempDirectories = time.Hour * 24

// The interval when temporary directories are monitored.
var tempDirectoriesMonitoringInterval = time.Hour * 4

// Defines an object which continuously watches and cleans the temporary directory of
// files that haven't been modified for maxTTLInTempDirectories (default 24 hours).
type TempLocationCleaner struct {
	util.AnyConfigAcceptor

	ticker *time.Ticker
}

func (self *TempLocationCleaner) Name() string {
	return "Temp Directory Cleaner"
}

func (self *TempLocationCleaner) Prepare(config *util.Config) {
	if self.ticker != nil {
		self.ticker.Stop()
	}

	if !config.CleanTempLocation {
		return
	}

	if config.CleanTempLocationTTLHours > 0 {
		maxTTLInTempDirectories = time.Hour*time.Duration(config.CleanTempLocationTTLHours)
	}

	if config.CleanTempLocationIntervalHours > 0 {
		tempDirectoriesMonitoringInterval = time.Hour*time.Duration(config.CleanTempLocationIntervalHours)
	}

	self.ticker = time.NewTicker(tempDirectoriesMonitoringInterval) // Clean temp locations every 4 hours.

	go func() {
		dirsToKeepClean := []string{os.TempDir()}
		// Run first
		self.cleanTempLocations(config, dirsToKeepClean)
		// Run in schedule
		for time := range self.ticker.C {
			util.GOut("temp", "Looking after temp locations at %v", time)
			self.waitForIdleIfRequired(config)
			self.cleanTempLocations(config, dirsToKeepClean)
		}
	}()
}

func (self *TempLocationCleaner) waitForIdleIfRequired(config *util.Config) {
	if config.CleanTempLocationOnlyWhenIDLE {
		for !util.NodeIsIdle.Get() {
			util.GOut("temp", "Waiting for node to become IDLE before cleaning temp locations.")
			time.Sleep(time.Minute * 5)
		}
	}
}

func (self *TempLocationCleaner) cleanTempLocations(config *util.Config, dirsToKeepClean []string) {
	for _, rootDir := range dirsToKeepClean {
		util.GOut("temp", "Cleaning expired files in %v", rootDir)

		dirToEmptyMap := map[string]bool{}
		expiredTimeOffset := time.Now().Add(-maxTTLInTempDirectories)

		// Handling outdated temporary files
		filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
				if err == nil {
					dirToEmptyMap[filepath.Dir(path)] = false

					if info.IsDir() {
						dirToEmptyMap[filepath.Clean(path)] = true
					} else {
						fileIsToRemove := true

						if fileIsToRemove && info.ModTime().After(expiredTimeOffset) {
							fileIsToRemove = false
						}

						if fileIsToRemove && len(config.CleanTempLocationExclusions) > 0 {
							for _, pattern := range config.CleanTempLocationExclusions {
								if matchesExclusionPattern, _ := filepath.Match(pattern, path); matchesExclusionPattern {
									fileIsToRemove = false
									break
								}
							}
						}

						if fileIsToRemove {
							if err := os.Remove(path); err == nil {
								util.GOut("temp", "Removed expired: %v", path)
							}
						}
					}
				}
				return err
			})

		// Handling all directories that are known to be empty
		for dirPath, emptyDir := range dirToEmptyMap {
			if emptyDir {
				if err := os.Remove(dirPath); err == nil {
					util.GOut("temp", "Removed empty directory: %v", dirPath)
				}
			}
		}
	}
}

// Registering the cleaner.
var _ = RegisterPreparer(new(TempLocationCleaner))
<commit_msg>TempCleaner: Added 5 minutes initial delay.<commit_after>// Copyright 2014 The jenkins-client-launcher Authors. All rights reserved.
// Use of this source code is governed by a MIT license that can be found in the LICENSE file.

package environment

import (
	"os"
	"github.com/jkellerer/jenkins-client-launcher/launcher/util"
	"path/filepath"
	"time"
)

// Max time to persist entries inside the temporary folders.
var maxTTLInTempDirectories = time.Hour * 24

// The interval when temporary directories are monitored.
var tempDirectoriesMonitoringInterval = time.Hour * 4

// Defines an object which continuously watches and cleans the temporary directory of
// files that haven't been modified for maxTTLInTempDirectories (default 24 hours).
type TempLocationCleaner struct {
	util.AnyConfigAcceptor

	ticker *time.Ticker
}

func (self *TempLocationCleaner) Name() string {
	return "Temp Directory Cleaner"
}

func (self *TempLocationCleaner) Prepare(config *util.Config) {
	if self.ticker != nil {
		self.ticker.Stop()
	}

	if !config.CleanTempLocation {
		return
	}

	if config.CleanTempLocationTTLHours > 0 {
		maxTTLInTempDirectories = time.Hour*time.Duration(config.CleanTempLocationTTLHours)
	}

	if config.CleanTempLocationIntervalHours > 0 {
		tempDirectoriesMonitoringInterval = time.Hour*time.Duration(config.CleanTempLocationIntervalHours)
	}

	self.ticker = time.NewTicker(tempDirectoriesMonitoringInterval) // Clean temp locations every 4 hours.

	go func() {
		dirsToKeepClean := []string{os.TempDir()}
		// Wait 5 minutes before clearing temp folders (let the client run first).
		time.Sleep(time.Minute * 5)
		// Run first
		self.waitForIdleIfRequired(config)
		self.cleanTempLocations(config, dirsToKeepClean)
		// Run in schedule
		for time := range self.ticker.C {
			util.GOut("temp", "Looking after temp locations at %v", time)
			self.waitForIdleIfRequired(config)
			self.cleanTempLocations(config, dirsToKeepClean)
		}
	}()
}

func (self *TempLocationCleaner) waitForIdleIfRequired(config *util.Config) {
	if config.CleanTempLocationOnlyWhenIDLE {
		for !util.NodeIsIdle.Get() {
			util.GOut("temp", "Waiting for node to become IDLE before cleaning temp locations.")
			time.Sleep(time.Minute * 5)
		}
	}
}

func (self *TempLocationCleaner) cleanTempLocations(config *util.Config, dirsToKeepClean []string) {
	for _, rootDir := range dirsToKeepClean {
		util.GOut("temp", "Cleaning expired files in %v", rootDir)

		dirToEmptyMap := map[string]bool{}
		expiredTimeOffset := time.Now().Add(-maxTTLInTempDirectories)

		// Handling outdated temporary files
		filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
				if err == nil {
					dirToEmptyMap[filepath.Dir(path)] = false

					if info.IsDir() {
						dirToEmptyMap[filepath.Clean(path)] = true
					} else {
						fileIsToRemove := true

						if fileIsToRemove && info.ModTime().After(expiredTimeOffset) {
							fileIsToRemove = false
						}

						if fileIsToRemove && len(config.CleanTempLocationExclusions) > 0 {
							for _, pattern := range config.CleanTempLocationExclusions {
								if matchesExclusionPattern, _ := filepath.Match(pattern, path); matchesExclusionPattern {
									fileIsToRemove = false
									break
								}
							}
						}

						if fileIsToRemove {
							if err := os.Remove(path); err == nil {
								util.GOut("temp", "Removed expired: %v", path)
							}
						}
					}
				}
				return err
			})

		// Handling all directories that are known to be empty
		for dirPath, emptyDir := range dirToEmptyMap {
			if emptyDir {
				if err := os.Remove(dirPath); err == nil {
					util.GOut("temp", "Removed empty directory: %v", dirPath)
				}
			}
		}
	}
}

// Registering the cleaner.
var _ = RegisterPreparer(new(TempLocationCleaner))
<|endoftext|>


<commit_before>package instructions

import "jvmgo/rtda"

// Compare float
type fcmpg struct {NoOperandsInstruction}
func (self *fcmpg) Execute(thread *rtda.Thread) {
    _fcmp(thread, true)
}

type fcmpl struct {NoOperandsInstruction}
func (self *fcmpl) Execute(thread *rtda.Thread) {
    _fcmp(thread, false)
}

func _fcmp(thread *rtda.Thread, gFlag bool) {
    stack := thread.CurrentFrame().OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    if v1 > v2 {
        stack.PushInt(1)
    } else if v1 == v2 {
        stack.PushInt(0)
    } else if v1 < v2 {
        stack.PushInt(-1)
    } else if gFlag {
        stack.PushInt(1)
    } else {
        stack.PushInt(-1)
    }
}
<commit_msg>refactor fcmp<commit_after>package instructions

import "jvmgo/rtda"

// Compare float
type fcmpg struct {NoOperandsInstruction}
func (self *fcmpg) Execute(frame *rtda.Frame) {
    _fcmp(frame, true)
}

type fcmpl struct {NoOperandsInstruction}
func (self *fcmpl) Execute(frame *rtda.Frame) {
    _fcmp(frame, false)
}

func _fcmp(frame *rtda.Frame, gFlag bool) {
    stack := frame.OperandStack()
    v2 := stack.PopFloat()
    v1 := stack.PopFloat()
    if v1 > v2 {
        stack.PushInt(1)
    } else if v1 == v2 {
        stack.PushInt(0)
    } else if v1 < v2 {
        stack.PushInt(-1)
    } else if gFlag {
        stack.PushInt(1)
    } else {
        stack.PushInt(-1)
    }
}
<|endoftext|>


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

package syscall

// Process operations for Darwin
// TODO:
// - getrusage
// - getrlimit, setrlimit

import (
	"syscall";
	"unsafe";
)
<commit_msg>getrusage on darwin; untested but builds.<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package syscall

// Process operations for Darwin
// TODO:
// - getrusage
// - getrlimit, setrlimit

import (
	"syscall";
	"unsafe";
)

func Getrusage(who int64, usage *Rusage) (ret, errno int64) {
	r1, r2, err := Syscall(SYS_GETRUSAGE, who, int64(uintptr(unsafe.Pointer(usage))), 0);
	return r1, err
}
<|endoftext|>


<commit_before>package docs

import (
	"bufio"
	"errors"
	"os"
	"path/filepath"
	"regexp"
	"testing"

	"github.com/NetSys/quilt/stitch"
	"github.com/NetSys/quilt/util"
)

const (
	blockStart     = "```javascript\n"
	blockEnd       = "```\n"
	commentPattern = "^\\[//\\]: # \\((.*)\\)\\W*$"
)

var errUnbalanced = errors.New("unbalanced code blocks")

type readmeParser struct {
	currentBlock string
	// Map block ID to code block.
	codeBlocks map[string]string
	recording  bool
}

func (parser *readmeParser) parse(line string) error {
	isStart := line == blockStart
	isEnd := line == blockEnd
	reComment := regexp.MustCompile(commentPattern)
	match := reComment.FindStringSubmatch(line)
	isComment := len(match) > 0

	if (isStart && parser.recording) || (isEnd && !parser.recording) {
		return errUnbalanced
	}

	switch {
	case isComment:
		parser.currentBlock = match[1]
	case isStart:
		parser.recording = true

		if parser.currentBlock == "" {
			return errors.New("missing code block id")
		}

		if _, ok := parser.codeBlocks[parser.currentBlock]; !ok {
			parser.codeBlocks[parser.currentBlock] = ""
		}
	case isEnd:
		parser.recording = false
		parser.currentBlock = ""
	}

	if parser.recording && !isStart {
		parser.codeBlocks[parser.currentBlock] += line
	}

	return nil
}

func (parser readmeParser) blocks() (map[string]string, error) {
	if parser.recording {
		return nil, errUnbalanced
	}
	return parser.codeBlocks, nil
}

func TestReadme(t *testing.T) {
	f, err := util.Open("../README.md")
	if err != nil {
		t.Errorf("Failed to open README: %s", err.Error())
		return
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)

	parser := readmeParser{}
	parser.codeBlocks = make(map[string]string)

	for scanner.Scan() {
		if err := parser.parse(scanner.Text() + "\n"); err != nil {
			t.Errorf("Failed to parse README: %s", err.Error())
			return
		}
	}

	if err := scanner.Err(); err != nil {
		t.Errorf("Failed to read README: %s", err.Error())
		return
	}

	blocks, err := parser.blocks()
	if err != nil {
		t.Errorf("Failed to parse README: %s", err.Error())
		return
	}

	goPath := os.Getenv("GOPATH")
	quiltPath := filepath.Join(goPath, "src")

	for _, block := range blocks {
		if err = checkConfig(block, quiltPath); err != nil {
			t.Errorf(err.Error())
		}
	}
}

func checkConfig(content string, quiltPath string) error {
	_, err := stitch.FromJavascript(content, stitch.ImportGetter{
		Path: quiltPath,
	})
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>docs: Remove network dependency in readme_test<commit_after>package docs

import (
	"bufio"
	"bytes"
	"errors"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"testing"

	"github.com/NetSys/quilt/stitch"
	"github.com/NetSys/quilt/util"
)

const (
	blockStart     = "```javascript\n"
	blockEnd       = "```\n"
	commentPattern = "^\\[//\\]: # \\((.*)\\)\\W*$"
)

var errUnbalanced = errors.New("unbalanced code blocks")

type readmeParser struct {
	currentBlock string
	// Map block ID to code block.
	codeBlocks map[string]string
	recording  bool
}

func (parser *readmeParser) parse(line string) error {
	isStart := line == blockStart
	isEnd := line == blockEnd
	reComment := regexp.MustCompile(commentPattern)
	match := reComment.FindStringSubmatch(line)
	isComment := len(match) > 0

	if (isStart && parser.recording) || (isEnd && !parser.recording) {
		return errUnbalanced
	}

	switch {
	case isComment:
		parser.currentBlock = match[1]
	case isStart:
		parser.recording = true

		if parser.currentBlock == "" {
			return errors.New("missing code block id")
		}

		if _, ok := parser.codeBlocks[parser.currentBlock]; !ok {
			parser.codeBlocks[parser.currentBlock] = ""
		}
	case isEnd:
		parser.recording = false
		parser.currentBlock = ""
	}

	if parser.recording && !isStart {
		parser.codeBlocks[parser.currentBlock] += line
	}

	return nil
}

func (parser readmeParser) blocks() (map[string]string, error) {
	if parser.recording {
		return nil, errUnbalanced
	}
	return parser.codeBlocks, nil
}

func TestReadme(t *testing.T) {
	f, err := util.Open("../README.md")
	if err != nil {
		t.Errorf("Failed to open README: %s", err.Error())
		return
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)

	parser := readmeParser{}
	parser.codeBlocks = make(map[string]string)

	for scanner.Scan() {
		if err := parser.parse(scanner.Text() + "\n"); err != nil {
			t.Errorf("Failed to parse README: %s", err.Error())
			return
		}
	}

	if err := scanner.Err(); err != nil {
		t.Errorf("Failed to read README: %s", err.Error())
		return
	}

	blocks, err := parser.blocks()
	if err != nil {
		t.Errorf("Failed to parse README: %s", err.Error())
		return
	}

	goPath := os.Getenv("GOPATH")
	quiltPath := filepath.Join(goPath, "src")

	for _, block := range blocks {
		if err = checkConfig(block, quiltPath); err != nil {
			t.Errorf(err.Error())
		}
	}
}

func checkConfig(content string, quiltPath string) error {
	oldHTTPGet := stitch.HTTPGet
	defer func() {
		stitch.HTTPGet = oldHTTPGet
	}()

	stitch.HTTPGet = func(url string) (*http.Response, error) {
		resp := http.Response{
			Body: ioutil.NopCloser(bytes.NewBufferString("")),
		}
		return &resp, nil
	}
	_, err := stitch.FromJavascript(content, stitch.ImportGetter{
		Path: quiltPath,
	})
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package lasr

import (
	"context"
	"errors"
	"fmt"

	"github.com/boltdb/bolt"
)

// Q is a first-in, first-out queue. Its methods are goroutine-safe.
type Q struct {
	db          *bolt.DB
	name        []byte
	root        [][]byte
	seq         Sequencer
	tokens      chan struct{}
	readyKey    []byte
	unackedKey  []byte
	returnedKey []byte
}

func (q *Q) String() string {
	return fmt.Sprintf("Q{Name: %q, Messages: %d}", string(q.name), len(q.tokens))
}

func (q *Q) nextSequence() (ID, error) {
	if q.seq != nil {
		return q.seq.NextSequence()
	}
	return q.nextUint64ID()
}

func newTokens() chan struct{} {
	return make(chan struct{}, int(^uint(0)>>1)) // max int
}

// NewQ creates a new Q.
func NewQ(db *bolt.DB, name string, options ...Option) (*Q, error) {
	bName := []byte(name)
	q := &Q{
		db:         db,
		name:       bName,
		tokens:     newTokens(),
		readyKey:   []byte("ready"),
		unackedKey: []byte("unacked"),
	}
	for _, o := range options {
		if err := o(q); err != nil {
			return nil, fmt.Errorf("lasr: couldn't create Q: %s", err)
		}
	}
	if err := q.init(); err != nil {
		return nil, err
	}
	return q, nil
}

func (q *Q) init() error {
	return q.db.Update(func(tx *bolt.Tx) error {
		bucket, err := q.bucket(tx, q.readyKey)
		if err != nil {
			return err
		}
		readyKeys := bucket.Stats().KeyN
		unacked, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		cursor := unacked.Cursor()
		// put unacked messages from previous session back in the queue
		for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
			if err := bucket.Put(k, v); err != nil {
				return err
			}
			readyKeys++
		}
		for i := 0; i < readyKeys; i++ {
			q.tokens <- struct{}{}
		}
		return nil
	})
}

// If Q has dead-lettering enabled, DeadLetters will return a dead letters
// queue that is the same as Q, but will emit dead letters on Receive.
// The dead letter queue itself does not support dead lettering; nacked
// messages that are not retried will be deleted.
//
// If dead-lettering  is not enabled on q, an error will be returned.
func DeadLetters(q *Q) (*Q, error) {
	if len(q.returnedKey) == 0 {
		return nil, errors.New("dead letters not available")
	}
	d := &Q{
		db:         q.db,
		name:       q.name,
		root:       q.root,
		seq:        q.seq,
		tokens:     newTokens(),
		readyKey:   q.returnedKey,
		unackedKey: []byte("deadletters-unacked"),
	}
	if err := d.init(); err != nil {
		return nil, err
	}
	return d, nil
}

type bucketer interface {
	CreateBucketIfNotExists([]byte) (*bolt.Bucket, error)
	Bucket([]byte) *bolt.Bucket
}

func (q *Q) rootBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
	var bucket bucketer = tx
	if len(q.root) > 0 {
		var err error
		for _, k := range q.root {
			bucket, err = bucket.CreateBucketIfNotExists(k)
			if err != nil {
				return nil, err
			}
		}
	}
	result, err := bucket.CreateBucketIfNotExists(q.name)

	return result, BoltError(err)
}

func (q *Q) bucket(tx *bolt.Tx, key []byte) (*bolt.Bucket, error) {
	bucket, err := q.rootBucket(tx)
	if err != nil {
		return nil, BoltError(err)
	}
	bucket, err = bucket.CreateBucketIfNotExists(key)
	if err != nil {
		return nil, BoltError(err)
	}
	return bucket, nil
}

func (q *Q) ack(id []byte) error {
	return q.db.Batch(func(tx *bolt.Tx) error {
		bucket, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		return bucket.Delete(id)
	})
}

func (q *Q) nack(id []byte, retry bool) error {
	return q.db.Update(func(tx *bolt.Tx) (rerr error) {
		bucket, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		val := bucket.Get(id)
		if retry {
			defer func() {
				if rerr == nil {
					q.tokens <- struct{}{}
				}
			}()
			ready, err := q.bucket(tx, q.readyKey)
			if err != nil {
				return err
			}
			return ready.Put(id, val)
		}
		if len(q.returnedKey) > 0 {
			returned, err := q.bucket(tx, q.returnedKey)
			if err != nil {
				return err
			}
			return returned.Put(id, val)
		}
		return nil
	})
}

func (q *Q) nextUint64ID() (Uint64ID, error) {
	var seq uint64
	var err error

	err = q.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(q.name)
		seq, err = bucket.NextSequence()
		return err
	})

	if err != nil {
		return Uint64ID(0), BoltError(err)
	}

	return Uint64ID(seq), nil
}

// Send sends a message to Q.
func (q *Q) Send(message []byte) error {
	id, err := q.nextSequence()
	if err != nil {
		return err
	}
	err = q.db.Update(func(tx *bolt.Tx) error {
		return q.send(id, message, tx)
	})
	if err == nil {
		q.tokens <- struct{}{}
	}
	return err
}

func (q *Q) send(id ID, body []byte, tx *bolt.Tx) error {
	key, err := id.MarshalBinary()
	if err != nil {
		return err
	}

	bucket, err := q.bucket(tx, q.readyKey)
	if err != nil {
		return BoltError(err)
	}

	if err := bucket.Put(key, body); err != nil {
		return BoltError(err)
	}
	return nil
}

// Receive receives a message from the queue. If no messages are available by
// the time the context is done, then the function will return a nil Message
// and the result of ctx.Err().
func (q *Q) Receive(ctx context.Context) (*Message, error) {
	select {
	case _, ok := <-q.tokens:
		if !ok {
			panic("tokens channel closed")
		}
	case <-ctx.Done():
		return nil, ctx.Err()
	}
	var (
		body, id []byte
		err      error
	)
	defer func() {
		if err != nil {
			// return the token
			q.tokens <- struct{}{}
		}
	}()

	err = q.db.Update(func(tx *bolt.Tx) error {
		ready, err := q.bucket(tx, q.readyKey)
		if err != nil {
			return err
		}
		cur := ready.Cursor()
		k, v := cur.First()
		if k == nil {
			return emptyQ
		}
		id = make([]byte, len(k))
		copy(id, k)
		body = make([]byte, len(v))
		copy(body, v)
		unacked, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		if err := unacked.Put(k, v); err != nil {
			return err
		}
		if err := ready.Delete(k); err != nil {
			return err
		}
		return nil
	})
	if err == emptyQ {
		panic("queue is empty and out of sync")
	}
	if err != nil {
		return nil, err
	}
	return &Message{Body: body, ID: id, status: Ready, q: q}, nil
}
<commit_msg>Move token handling inside of bolt tx.<commit_after>package lasr

import (
	"context"
	"errors"
	"fmt"

	"github.com/boltdb/bolt"
)

// Q is a first-in, first-out queue. Its methods are goroutine-safe.
type Q struct {
	db          *bolt.DB
	name        []byte
	root        [][]byte
	seq         Sequencer
	tokens      chan struct{}
	readyKey    []byte
	unackedKey  []byte
	returnedKey []byte
}

func (q *Q) String() string {
	return fmt.Sprintf("Q{Name: %q, Messages: %d}", string(q.name), len(q.tokens))
}

func (q *Q) nextSequence() (ID, error) {
	if q.seq != nil {
		return q.seq.NextSequence()
	}
	return q.nextUint64ID()
}

func newTokens() chan struct{} {
	return make(chan struct{}, int(^uint(0)>>1)) // max int
}

// NewQ creates a new Q.
func NewQ(db *bolt.DB, name string, options ...Option) (*Q, error) {
	bName := []byte(name)
	q := &Q{
		db:         db,
		name:       bName,
		tokens:     newTokens(),
		readyKey:   []byte("ready"),
		unackedKey: []byte("unacked"),
	}
	for _, o := range options {
		if err := o(q); err != nil {
			return nil, fmt.Errorf("lasr: couldn't create Q: %s", err)
		}
	}
	if err := q.init(); err != nil {
		return nil, err
	}
	return q, nil
}

func (q *Q) init() error {
	return q.db.Update(func(tx *bolt.Tx) error {
		bucket, err := q.bucket(tx, q.readyKey)
		if err != nil {
			return err
		}
		readyKeys := bucket.Stats().KeyN
		unacked, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		cursor := unacked.Cursor()
		// put unacked messages from previous session back in the queue
		for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
			if err := bucket.Put(k, v); err != nil {
				return err
			}
			readyKeys++
		}
		for i := 0; i < readyKeys; i++ {
			q.tokens <- struct{}{}
		}
		return nil
	})
}

// If Q has dead-lettering enabled, DeadLetters will return a dead letters
// queue that is the same as Q, but will emit dead letters on Receive.
// The dead letter queue itself does not support dead lettering; nacked
// messages that are not retried will be deleted.
//
// If dead-lettering  is not enabled on q, an error will be returned.
func DeadLetters(q *Q) (*Q, error) {
	if len(q.returnedKey) == 0 {
		return nil, errors.New("dead letters not available")
	}
	d := &Q{
		db:         q.db,
		name:       q.name,
		root:       q.root,
		seq:        q.seq,
		tokens:     newTokens(),
		readyKey:   q.returnedKey,
		unackedKey: []byte("deadletters-unacked"),
	}
	if err := d.init(); err != nil {
		return nil, err
	}
	return d, nil
}

type bucketer interface {
	CreateBucketIfNotExists([]byte) (*bolt.Bucket, error)
	Bucket([]byte) *bolt.Bucket
}

func (q *Q) rootBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
	var bucket bucketer = tx
	if len(q.root) > 0 {
		var err error
		for _, k := range q.root {
			bucket, err = bucket.CreateBucketIfNotExists(k)
			if err != nil {
				return nil, err
			}
		}
	}
	result, err := bucket.CreateBucketIfNotExists(q.name)

	return result, BoltError(err)
}

func (q *Q) bucket(tx *bolt.Tx, key []byte) (*bolt.Bucket, error) {
	bucket, err := q.rootBucket(tx)
	if err != nil {
		return nil, BoltError(err)
	}
	bucket, err = bucket.CreateBucketIfNotExists(key)
	if err != nil {
		return nil, BoltError(err)
	}
	return bucket, nil
}

func (q *Q) ack(id []byte) error {
	return q.db.Batch(func(tx *bolt.Tx) error {
		bucket, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		return bucket.Delete(id)
	})
}

func (q *Q) nack(id []byte, retry bool) error {
	return q.db.Update(func(tx *bolt.Tx) (rerr error) {
		bucket, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		val := bucket.Get(id)
		if retry {
			defer func() {
				if rerr == nil {
					q.tokens <- struct{}{}
				}
			}()
			ready, err := q.bucket(tx, q.readyKey)
			if err != nil {
				return err
			}
			return ready.Put(id, val)
		}
		if len(q.returnedKey) > 0 {
			returned, err := q.bucket(tx, q.returnedKey)
			if err != nil {
				return err
			}
			return returned.Put(id, val)
		}
		return nil
	})
}

func (q *Q) nextUint64ID() (Uint64ID, error) {
	var seq uint64
	var err error

	err = q.db.Update(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(q.name)
		seq, err = bucket.NextSequence()
		return err
	})

	if err != nil {
		return Uint64ID(0), BoltError(err)
	}

	return Uint64ID(seq), nil
}

// Send sends a message to Q.
func (q *Q) Send(message []byte) error {
	id, err := q.nextSequence()
	if err != nil {
		return err
	}
	return q.db.Update(func(tx *bolt.Tx) (err error) {
		defer func() {
			if err == nil {
				q.tokens <- struct{}{}
			}
		}()
		return q.send(id, message, tx)
	})
}

func (q *Q) send(id ID, body []byte, tx *bolt.Tx) error {
	key, err := id.MarshalBinary()
	if err != nil {
		return err
	}

	bucket, err := q.bucket(tx, q.readyKey)
	if err != nil {
		return BoltError(err)
	}

	if err := bucket.Put(key, body); err != nil {
		return BoltError(err)
	}
	return nil
}

// Receive receives a message from the queue. If no messages are available by
// the time the context is done, then the function will return a nil Message
// and the result of ctx.Err().
func (q *Q) Receive(ctx context.Context) (*Message, error) {
	select {
	case _, ok := <-q.tokens:
		if !ok {
			panic("tokens channel closed")
		}
	case <-ctx.Done():
		return nil, ctx.Err()
	}
	var (
		body, id []byte
		err      error
	)
	err = q.db.Update(func(tx *bolt.Tx) (err error) {
		defer func() {
			if err != nil {
				// return the token
				q.tokens <- struct{}{}
			}
		}()

		ready, err := q.bucket(tx, q.readyKey)
		if err != nil {
			return err
		}
		cur := ready.Cursor()
		k, v := cur.First()
		if k == nil {
			return emptyQ
		}
		id = make([]byte, len(k))
		copy(id, k)
		body = make([]byte, len(v))
		copy(body, v)
		unacked, err := q.bucket(tx, q.unackedKey)
		if err != nil {
			return err
		}
		if err := unacked.Put(k, v); err != nil {
			return err
		}
		if err := ready.Delete(k); err != nil {
			return err
		}
		return nil
	})
	if err == emptyQ {
		panic("queue is empty and out of sync")
	}
	if err != nil {
		return nil, err
	}
	return &Message{Body: body, ID: id, status: Ready, q: q}, nil
}
<|endoftext|>