<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxc

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"launchpad.net/golxc"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/container"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/cloudinit"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api"
	"launchpad.net/loggo"
)

var logger = loggo.GetLogger("juju.container.lxc")

var (
	defaultTemplate     = "ubuntu-cloud"
	containerDir        = "/var/lib/juju/containers"
	removedContainerDir = "/var/lib/juju/removed-containers"
	lxcContainerDir     = "/var/lib/lxc"
)

type ContainerFactory interface {
	NewContainer(machineId string) (container.Container, error)
	NewFromExisting(existing golxc.Container) (container.Container, error)
}

type lxcFactory struct {
	lxc golxc.ContainerFactory
}

func NewFactory(factory golxc.ContainerFactory) ContainerFactory {
	return &lxcFactory{factory}
}

type lxcContainer struct {
	golxc.Container
	machineId string
}

// TODO(thumper): care about constraints...
func (factory *lxcFactory) NewContainer(machineId string) (container.Container, error) {
	name := state.MachineTag(machineId)
	return &lxcContainer{
		Container: factory.lxc.New(name),
		machineId: machineId,
	}, nil
}

func (factory *lxcFactory) NewFromExisting(existing golxc.Container) (container.Container, error) {
	machineId := state.MachineIdFromTag(existing.Name())
	return &lxcContainer{
		Container: existing,
		machineId: machineId,
	}, nil
}

func (lxc *lxcContainer) Create(
	series, nonce string,
	tools *state.Tools,
	environConfig *config.Config,
	stateInfo *state.Info,
	apiInfo *api.Info,
) error {
	// Create the cloud-init.
	directory := lxc.Directory()
	logger.Tracef("create directory: %s", directory)
	if err := os.MkdirAll(directory, 0755); err != nil {
		logger.Errorf("failed to create container directory: %v", err)
		return err
	}
	logger.Tracef("write cloud-init")
	userDataFilename, err := lxc.WriteUserData(nonce, tools, environConfig, stateInfo, apiInfo)
	if err != nil {
		logger.Errorf("failed to write user data: %v", err)
		return err
	}
	logger.Tracef("write the lxc.conf file")
	configFile, err := lxc.WriteConfig()
	if err != nil {
		logger.Errorf("failed to write config file: %v", err)
		return err
	}
	templateParams := []string{
		"--debug",                      // Debug errors in the cloud image
		"--userdata", userDataFilename, // Our groovey cloud-init
		"--hostid", lxc.Name(), // Use the container name as the hostid
		"-r", series,
	}
	// Create the container.
	logger.Tracef("create the container")
	if err := lxc.Container.Create(configFile, defaultTemplate, templateParams...); err != nil {
		logger.Errorf("lxc container creation failed: %v", err)
		return err
	}
	// Make sure that the mount dir has been created.
	logger.Tracef("make the mount dir for the shard logs")
	if err := os.MkdirAll(lxc.InternalLogDir(), 0755); err != nil {
		logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err)
		return err
	}
	logger.Tracef("lxc container created")
	return nil
}

func (lxc *lxcContainer) Start() error {

	// Start the lxc container with the appropriate settings for grabbing the
	// console output and a log file.
	directory := lxc.Directory()
	consoleFile := filepath.Join(directory, "console.log")
	lxc.Container.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug)
	// Experimentation has shown that passing the config file through at start
	// time when it has mount points defined, causes those mounts to fail, and
	// the container fails to start.  Passing the same config through at
	// create time seems to work fine.
	configFile := ""
	logger.Tracef("start the container")
	err := lxc.Container.Start(configFile, consoleFile)
	logger.Tracef("container started")
	return err
}

// Defer the Stop method to the composed lxc.Container

func (lxc *lxcContainer) Destroy() error {
	err := lxc.Container.Destroy()
	if err != nil {
		logger.Errorf("failed to destroy lxc container: %v", err)
		return err
	}
	// Move the directory.
	logger.Tracef("create old container dir: %s", removedContainerDir)
	if err := os.MkdirAll(removedContainerDir, 0755); err != nil {
		logger.Errorf("failed to create removed container directory: %v", err)
		return err
	}
	removedDir := uniqueDirectory(removedContainerDir, string(lxc.Id()))
	if err := os.Rename(lxc.Directory(), removedDir); err != nil {
		logger.Errorf("failed to rename container directory: %v", err)
		return err
	}
	return nil
}

// TODO: Destroy should also remove the directory... (or rename it and save it for later analysis)

func (lxc *lxcContainer) Directory() string {
	return filepath.Join(containerDir, lxc.Name())
}

const internalLogDir = "%s/%s/rootfs/var/log/juju"

func (lxc *lxcContainer) InternalLogDir() string {
	return fmt.Sprintf(internalLogDir, lxcContainerDir, lxc.Name())
}

const localConfig = `
lxc.network.type = veth
lxc.network.link = lxcbr0
lxc.network.flags = up

lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0
`

func (lxc *lxcContainer) WriteConfig() (string, error) {
	// TODO(thumper): support different network settings.
	configFilename := filepath.Join(lxc.Directory(), "lxc.conf")
	if err := ioutil.WriteFile(configFilename, []byte(localConfig), 0644); err != nil {
		return "", err
	}
	return configFilename, nil
}

func (lxc *lxcContainer) WriteUserData(
	nonce string,
	tools *state.Tools,
	environConfig *config.Config,
	stateInfo *state.Info,
	apiInfo *api.Info,
) (string, error) {
	userData, err := lxc.userData(nonce, tools, environConfig, stateInfo, apiInfo)
	if err != nil {
		logger.Errorf("failed to create user data: %v", err)
		return "", err
	}
	userDataFilename := filepath.Join(lxc.Directory(), "cloud-init")
	if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil {
		logger.Errorf("failed to write user data: %v", err)
		return "", err
	}
	return userDataFilename, nil
}

func (lxc *lxcContainer) userData(
	nonce string,
	tools *state.Tools,
	environConfig *config.Config,
	stateInfo *state.Info,
	apiInfo *api.Info,
) ([]byte, error) {
	machineConfig := &cloudinit.MachineConfig{
		MachineId:            lxc.machineId,
		MachineNonce:         nonce,
		MachineContainerType: "lxc",
		StateInfo:            stateInfo,
		APIInfo:              apiInfo,
		DataDir:              "/var/lib/juju",
		Tools:                tools,
	}
	if err := environs.FinishMachineConfig(machineConfig, environConfig, constraints.Value{}); err != nil {
		return nil, err
	}
	cloudConfig, err := cloudinit.New(machineConfig)
	if err != nil {
		return nil, err
	}
	data, err := cloudConfig.Render()
	if err != nil {
		return nil, err
	}
	return data, nil
}

// Id returns a provider-generated identifier for the Instance.
func (lxc *lxcContainer) Id() instance.Id {
	return instance.Id(lxc.Name())
}

// DNSName returns the DNS name for the instance.
// If the name is not yet allocated, it will return
// an ErrNoDNSName error.
func (lxc *lxcContainer) DNSName() (string, error) {
	return "", instance.ErrNoDNSName
}

// WaitDNSName returns the DNS name for the instance,
// waiting until it is allocated if necessary.
func (lxc *lxcContainer) WaitDNSName() (string, error) {
	return "", instance.ErrNoDNSName
}

// OpenPorts opens the given ports on the instance, which
// should have been started with the given machine id.
func (lxc *lxcContainer) OpenPorts(machineId string, ports []instance.Port) error {
	return fmt.Errorf("not implemented")
}

// ClosePorts closes the given ports on the instance, which
// should have been started with the given machine id.
func (lxc *lxcContainer) ClosePorts(machineId string, ports []instance.Port) error {
	return fmt.Errorf("not implemented")
}

// Ports returns the set of ports open on the instance, which
// should have been started with the given machine id.
// The ports are returned as sorted by state.SortPorts.
func (lxc *lxcContainer) Ports(machineId string) ([]instance.Port, error) {
	return nil, fmt.Errorf("not implemented")
}

// uniqueDirectory returns "path/name" if that directory doesn't exist.  If it
// does, the method starts appending .1, .2, etc until a unique name is found.
func uniqueDirectory(path, name string) string {
	dir := filepath.Join(path, name)
	_, err := os.Stat(dir)
	if os.IsNotExist(err) {
		return dir
	}
	for i := 1; ; i++ {
		dir := filepath.Join(path, fmt.Sprintf("%s.%d", name, i))
		_, err := os.Stat(dir)
		if os.IsNotExist(err) {
			return dir
		}
	}
	panic("never reached.")
}
<commit_msg>Remove comment.<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxc

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"launchpad.net/golxc"
	"launchpad.net/juju-core/constraints"
	"launchpad.net/juju-core/container"
	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/cloudinit"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/state"
	"launchpad.net/juju-core/state/api"
	"launchpad.net/loggo"
)

var logger = loggo.GetLogger("juju.container.lxc")

var (
	defaultTemplate     = "ubuntu-cloud"
	containerDir        = "/var/lib/juju/containers"
	removedContainerDir = "/var/lib/juju/removed-containers"
	lxcContainerDir     = "/var/lib/lxc"
)

type ContainerFactory interface {
	NewContainer(machineId string) (container.Container, error)
	NewFromExisting(existing golxc.Container) (container.Container, error)
}

type lxcFactory struct {
	lxc golxc.ContainerFactory
}

func NewFactory(factory golxc.ContainerFactory) ContainerFactory {
	return &lxcFactory{factory}
}

type lxcContainer struct {
	golxc.Container
	machineId string
}

// TODO(thumper): care about constraints...
func (factory *lxcFactory) NewContainer(machineId string) (container.Container, error) {
	name := state.MachineTag(machineId)
	return &lxcContainer{
		Container: factory.lxc.New(name),
		machineId: machineId,
	}, nil
}

func (factory *lxcFactory) NewFromExisting(existing golxc.Container) (container.Container, error) {
	machineId := state.MachineIdFromTag(existing.Name())
	return &lxcContainer{
		Container: existing,
		machineId: machineId,
	}, nil
}

func (lxc *lxcContainer) Create(
	series, nonce string,
	tools *state.Tools,
	environConfig *config.Config,
	stateInfo *state.Info,
	apiInfo *api.Info,
) error {
	// Create the cloud-init.
	directory := lxc.Directory()
	logger.Tracef("create directory: %s", directory)
	if err := os.MkdirAll(directory, 0755); err != nil {
		logger.Errorf("failed to create container directory: %v", err)
		return err
	}
	logger.Tracef("write cloud-init")
	userDataFilename, err := lxc.WriteUserData(nonce, tools, environConfig, stateInfo, apiInfo)
	if err != nil {
		logger.Errorf("failed to write user data: %v", err)
		return err
	}
	logger.Tracef("write the lxc.conf file")
	configFile, err := lxc.WriteConfig()
	if err != nil {
		logger.Errorf("failed to write config file: %v", err)
		return err
	}
	templateParams := []string{
		"--debug",                      // Debug errors in the cloud image
		"--userdata", userDataFilename, // Our groovey cloud-init
		"--hostid", lxc.Name(), // Use the container name as the hostid
		"-r", series,
	}
	// Create the container.
	logger.Tracef("create the container")
	if err := lxc.Container.Create(configFile, defaultTemplate, templateParams...); err != nil {
		logger.Errorf("lxc container creation failed: %v", err)
		return err
	}
	// Make sure that the mount dir has been created.
	logger.Tracef("make the mount dir for the shard logs")
	if err := os.MkdirAll(lxc.InternalLogDir(), 0755); err != nil {
		logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err)
		return err
	}
	logger.Tracef("lxc container created")
	return nil
}

func (lxc *lxcContainer) Start() error {

	// Start the lxc container with the appropriate settings for grabbing the
	// console output and a log file.
	directory := lxc.Directory()
	consoleFile := filepath.Join(directory, "console.log")
	lxc.Container.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug)
	// Experimentation has shown that passing the config file through at start
	// time when it has mount points defined, causes those mounts to fail, and
	// the container fails to start.  Passing the same config through at
	// create time seems to work fine.
	configFile := ""
	logger.Tracef("start the container")
	err := lxc.Container.Start(configFile, consoleFile)
	logger.Tracef("container started")
	return err
}

// Defer the Stop method to the composed lxc.Container

func (lxc *lxcContainer) Destroy() error {
	err := lxc.Container.Destroy()
	if err != nil {
		logger.Errorf("failed to destroy lxc container: %v", err)
		return err
	}
	// Move the directory.
	logger.Tracef("create old container dir: %s", removedContainerDir)
	if err := os.MkdirAll(removedContainerDir, 0755); err != nil {
		logger.Errorf("failed to create removed container directory: %v", err)
		return err
	}
	removedDir := uniqueDirectory(removedContainerDir, string(lxc.Id()))
	if err := os.Rename(lxc.Directory(), removedDir); err != nil {
		logger.Errorf("failed to rename container directory: %v", err)
		return err
	}
	return nil
}

func (lxc *lxcContainer) Directory() string {
	return filepath.Join(containerDir, lxc.Name())
}

const internalLogDir = "%s/%s/rootfs/var/log/juju"

func (lxc *lxcContainer) InternalLogDir() string {
	return fmt.Sprintf(internalLogDir, lxcContainerDir, lxc.Name())
}

const localConfig = `
lxc.network.type = veth
lxc.network.link = lxcbr0
lxc.network.flags = up

lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0
`

func (lxc *lxcContainer) WriteConfig() (string, error) {
	// TODO(thumper): support different network settings.
	configFilename := filepath.Join(lxc.Directory(), "lxc.conf")
	if err := ioutil.WriteFile(configFilename, []byte(localConfig), 0644); err != nil {
		return "", err
	}
	return configFilename, nil
}

func (lxc *lxcContainer) WriteUserData(
	nonce string,
	tools *state.Tools,
	environConfig *config.Config,
	stateInfo *state.Info,
	apiInfo *api.Info,
) (string, error) {
	userData, err := lxc.userData(nonce, tools, environConfig, stateInfo, apiInfo)
	if err != nil {
		logger.Errorf("failed to create user data: %v", err)
		return "", err
	}
	userDataFilename := filepath.Join(lxc.Directory(), "cloud-init")
	if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil {
		logger.Errorf("failed to write user data: %v", err)
		return "", err
	}
	return userDataFilename, nil
}

func (lxc *lxcContainer) userData(
	nonce string,
	tools *state.Tools,
	environConfig *config.Config,
	stateInfo *state.Info,
	apiInfo *api.Info,
) ([]byte, error) {
	machineConfig := &cloudinit.MachineConfig{
		MachineId:            lxc.machineId,
		MachineNonce:         nonce,
		MachineContainerType: "lxc",
		StateInfo:            stateInfo,
		APIInfo:              apiInfo,
		DataDir:              "/var/lib/juju",
		Tools:                tools,
	}
	if err := environs.FinishMachineConfig(machineConfig, environConfig, constraints.Value{}); err != nil {
		return nil, err
	}
	cloudConfig, err := cloudinit.New(machineConfig)
	if err != nil {
		return nil, err
	}
	data, err := cloudConfig.Render()
	if err != nil {
		return nil, err
	}
	return data, nil
}

// Id returns a provider-generated identifier for the Instance.
func (lxc *lxcContainer) Id() instance.Id {
	return instance.Id(lxc.Name())
}

// DNSName returns the DNS name for the instance.
// If the name is not yet allocated, it will return
// an ErrNoDNSName error.
func (lxc *lxcContainer) DNSName() (string, error) {
	return "", instance.ErrNoDNSName
}

// WaitDNSName returns the DNS name for the instance,
// waiting until it is allocated if necessary.
func (lxc *lxcContainer) WaitDNSName() (string, error) {
	return "", instance.ErrNoDNSName
}

// OpenPorts opens the given ports on the instance, which
// should have been started with the given machine id.
func (lxc *lxcContainer) OpenPorts(machineId string, ports []instance.Port) error {
	return fmt.Errorf("not implemented")
}

// ClosePorts closes the given ports on the instance, which
// should have been started with the given machine id.
func (lxc *lxcContainer) ClosePorts(machineId string, ports []instance.Port) error {
	return fmt.Errorf("not implemented")
}

// Ports returns the set of ports open on the instance, which
// should have been started with the given machine id.
// The ports are returned as sorted by state.SortPorts.
func (lxc *lxcContainer) Ports(machineId string) ([]instance.Port, error) {
	return nil, fmt.Errorf("not implemented")
}

// uniqueDirectory returns "path/name" if that directory doesn't exist.  If it
// does, the method starts appending .1, .2, etc until a unique name is found.
func uniqueDirectory(path, name string) string {
	dir := filepath.Join(path, name)
	_, err := os.Stat(dir)
	if os.IsNotExist(err) {
		return dir
	}
	for i := 1; ; i++ {
		dir := filepath.Join(path, fmt.Sprintf("%s.%d", name, i))
		_, err := os.Stat(dir)
		if os.IsNotExist(err) {
			return dir
		}
	}
	panic("never reached.")
}
<|endoftext|>


<commit_before>package text

import (
	"image"
	"image/draw"
	"unicode"

	"github.com/faiface/pixel"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

type Glyph struct {
	Orig    pixel.Vec
	Frame   pixel.Rect
	Advance float64
}

type Atlas struct {
	pic        pixel.Picture
	mapping    map[rune]Glyph
	kern       map[struct{ r0, r1 rune }]float64
	lineHeight float64
}

func NewAtlas(face font.Face, runes []rune) *Atlas {
	//FIXME: don't put glyphs in just one row, make a square

	width := fixed.Int26_6(0)
	for _, r := range runes {
		b, _, ok := face.GlyphBounds(r)
		if !ok && r != unicode.ReplacementChar {
			continue
		}
		width += b.Max.X - b.Min.X

		// padding to avoid filtering artifacts
		width = fixed.I(width.Ceil())
		width += fixed.I(2)
	}

	atlasImg := image.NewRGBA(image.Rect(
		0, 0,
		width.Ceil(), (face.Metrics().Ascent + face.Metrics().Descent).Ceil(),
	))
	atlasHeight := float64(atlasImg.Bounds().Dy())

	mapping := make(map[rune]Glyph)

	dot := fixed.Point26_6{
		X: 0,
		Y: face.Metrics().Ascent,
	}

	for _, r := range runes {
		b, _, ok := face.GlyphBounds(r)
		if !ok && r != unicode.ReplacementChar {
			continue
		}

		dot.X -= b.Min.X

		dr, mask, maskp, _, _ := face.Glyph(dot, r)
		draw.Draw(atlasImg, dr, mask, maskp, draw.Src)

		orig := pixel.V(
			float64(dot.X)/(1<<6),
			atlasHeight-float64(dot.Y)/(1<<6),
		)

		frame := pixel.R(
			float64(dr.Min.X),
			atlasHeight-float64(dr.Min.Y),
			float64(dr.Max.X),
			atlasHeight-float64(dr.Max.Y),
		).Norm()

		adv, _ := face.GlyphAdvance(r)
		advance := float64(adv) / (1 << 6)

		mapping[r] = Glyph{orig, frame, advance}

		dot.X += b.Max.X

		// padding
		dot.X = fixed.I(dot.X.Ceil())
		dot.X += fixed.I(2)
	}

	kern := make(map[struct{ r0, r1 rune }]float64)
	for _, r0 := range runes {
		for _, r1 := range runes {
			kern[struct{ r0, r1 rune }{r0, r1}] = float64(face.Kern(r0, r1)) / (1 << 6)
		}
	}

	return &Atlas{
		pixel.PictureDataFromImage(atlasImg),
		mapping,
		kern,
		float64(face.Metrics().Height) / (1 << 6),
	}
}

func (a *Atlas) Picture() pixel.Picture {
	return a.pic
}

func (a *Atlas) Contains(r rune) bool {
	_, ok := a.mapping[r]
	return ok
}

func (a *Atlas) Glyph(r rune) Glyph {
	return a.mapping[r]
}

func (a *Atlas) Kern(r0, r1 rune) float64 {
	return a.kern[struct{ r0, r1 rune }{r0, r1}]
}

func (a *Atlas) LineHeight() float64 {
	return a.lineHeight
}
<commit_msg>add Text.Bounds, Atlas.Ascent, Atlas.Descent<commit_after>package text

import (
	"image"
	"image/draw"
	"unicode"

	"github.com/faiface/pixel"
	"golang.org/x/image/font"
	"golang.org/x/image/math/fixed"
)

type Glyph struct {
	Orig    pixel.Vec
	Frame   pixel.Rect
	Advance float64
}

type Atlas struct {
	pic        pixel.Picture
	mapping    map[rune]Glyph
	kern       map[struct{ r0, r1 rune }]float64
	ascent     float64
	descent    float64
	lineHeight float64
}

func NewAtlas(face font.Face, runes []rune) *Atlas {
	//FIXME: don't put glyphs in just one row, make a square

	width := fixed.Int26_6(0)
	for _, r := range runes {
		b, _, ok := face.GlyphBounds(r)
		if !ok && r != unicode.ReplacementChar {
			continue
		}
		width += b.Max.X - b.Min.X

		// padding to avoid filtering artifacts
		width = fixed.I(width.Ceil())
		width += fixed.I(2)
	}

	atlasImg := image.NewRGBA(image.Rect(
		0, 0,
		width.Ceil(), (face.Metrics().Ascent + face.Metrics().Descent).Ceil(),
	))
	atlasHeight := float64(atlasImg.Bounds().Dy())

	mapping := make(map[rune]Glyph)

	dot := fixed.Point26_6{
		X: 0,
		Y: face.Metrics().Ascent,
	}

	for _, r := range runes {
		b, _, ok := face.GlyphBounds(r)
		if !ok && r != unicode.ReplacementChar {
			continue
		}

		dot.X -= b.Min.X

		dr, mask, maskp, _, _ := face.Glyph(dot, r)
		draw.Draw(atlasImg, dr, mask, maskp, draw.Src)

		orig := pixel.V(
			float64(dot.X)/(1<<6),
			atlasHeight-float64(dot.Y)/(1<<6),
		)

		frame := pixel.R(
			float64(dr.Min.X),
			atlasHeight-float64(dr.Min.Y),
			float64(dr.Max.X),
			atlasHeight-float64(dr.Max.Y),
		).Norm()

		adv, _ := face.GlyphAdvance(r)
		advance := float64(adv) / (1 << 6)

		mapping[r] = Glyph{orig, frame, advance}

		dot.X += b.Max.X

		// padding
		dot.X = fixed.I(dot.X.Ceil())
		dot.X += fixed.I(2)
	}

	kern := make(map[struct{ r0, r1 rune }]float64)
	for _, r0 := range runes {
		for _, r1 := range runes {
			kern[struct{ r0, r1 rune }{r0, r1}] = float64(face.Kern(r0, r1)) / (1 << 6)
		}
	}

	return &Atlas{
		pic:        pixel.PictureDataFromImage(atlasImg),
		mapping:    mapping,
		kern:       kern,
		ascent:     float64(face.Metrics().Ascent) / (1 << 6),
		descent:    float64(face.Metrics().Descent) / (1 << 6),
		lineHeight: float64(face.Metrics().Height) / (1 << 6),
	}
}

func (a *Atlas) Picture() pixel.Picture {
	return a.pic
}

func (a *Atlas) Contains(r rune) bool {
	_, ok := a.mapping[r]
	return ok
}

func (a *Atlas) Glyph(r rune) Glyph {
	return a.mapping[r]
}

func (a *Atlas) Kern(r0, r1 rune) float64 {
	return a.kern[struct{ r0, r1 rune }{r0, r1}]
}

func (a *Atlas) Ascent() float64 {
	return a.ascent
}

func (a *Atlas) Descent() float64 {
	return a.descent
}

func (a *Atlas) LineHeight() float64 {
	return a.lineHeight
}
<|endoftext|>


<commit_before>package synq

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
)

// These Amazon Web Services credentials are provided to the AWS SDK, which is
// used to upload content in multiple parts. There is no IAM user with these
// credentials; they are supplied because the AWS SDK requires some credentials
// to attempt to start uploading. This package replaces the AWS SDK's request
// signing method with its own method.
const multipartUploadAwsAccessKeyId = "AAAAAAAAAAAAAAAAAAAA"
const multipartUploadAwsSecretAccessKey = "ssssssssssssssssssssssssssssssssssssssss"

// UploaderSignatureUrlFormat is a printf format string which is used when
// signing multipart upload requests.
// TODO(mastensg): Determine this format (or at least prefix) at runtime, from
// the Synq HTTP API.
const UploaderSignatureUrlFormat = "https://uploader.synq.fm/uploader/signature/%s?token=%s"

// UploaderSignatureRequest is the request that is sent when using the
// embeddable web uploader's request signing service.
type UploaderSignatureRequest struct {
	Headers string `json:"headers"`
}

// UploaderSignatureResponse is the response that is received when using the
// embeddable web uploader's request signing service.
type UploaderSignatureResponse struct {
	Signature string `json:"signature"`
}

// UploaderSignature uses the backend of the embeddable web uploader to sign
// multipart upload requests.
func UploaderSignature(url_fmt, video_id, token, headers string) ([]byte, error) {
	url := fmt.Sprintf(url_fmt, video_id, token)

	// construct request body
	reqStruct := UploaderSignatureRequest{Headers: headers}
	reqBody, err := json.Marshal(reqStruct)
	if err != nil {
		return nil, err
	}

	// perform request
	resp, err := http.Post(url, "application/json", bytes.NewReader(reqBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// read response
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	// parse response
	respStruct := UploaderSignatureResponse{}
	err = json.Unmarshal(respBody, &respStruct)
	if err != nil {
		return nil, err
	}

	return []byte(respStruct.Signature), nil
}

// bucketOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the bucket name part of that URL.
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         bucket, err := bucketOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(bucket) // prints synqfm
func bucketOfUploadAction(actionURL string) (string, error) {
	u, err := url.Parse(actionURL)
	if err != nil {
		return "", err
	}

	hs := strings.Split(u.Host, ".")
	if len(hs) != 4 {
		return "", errors.New("Invalid action URL. " +
			"Not exactly 4 period-separated words in host.")
	}
	if !strings.HasPrefix(hs[1], "s3") {
		return "", errors.New("Invalid action URL. " +
			"Second word in period-separated host does not start with s3")
	}
	if hs[2] != "amazonaws" {
		return "", errors.New("Invalid action URL. " +
			"Third word in period-separated host is not amazonaws")
	}
	if hs[3] != "com" {
		return "", errors.New("Invalid action URL. " +
			"Fourth word in period-separated host is not com")
	}

	return hs[0], nil
}

// regionOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the region of the bucket that is the URL refers to.
//
// This relies on heuristics, and will not work with certain styles of URLs.
//
// See: http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         region, err := regionOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(region) // prints us-east-1
func regionOfUploadAction(actionURL string) (string, error) {
	u, err := url.Parse(actionURL)
	if err != nil {
		return "", err
	}

	hs := strings.Split(u.Host, ".")
	if len(hs) != 4 {
		return "", errors.New("Invalid action URL. " +
			"Not exactly 4 period-separated words in host.")
	}
	if !strings.HasPrefix(hs[1], "s3") {
		return "", errors.New("Invalid action URL. " +
			"Second word in period-separated host does not start with s3")
	}
	if hs[2] != "amazonaws" {
		return "", errors.New("Invalid action URL. " +
			"Third word in period-separated host is not amazonaws")
	}
	if hs[3] != "com" {
		return "", errors.New("Invalid action URL. " +
			"Fourth word in period-separated host is not com")
	}

	regionPart := hs[1]

	// us-east-1 is the region if nothing else is specified.
	if regionPart == "s3" {
		return "us-east-1", nil
	}

	// If the region part of the host is not exactly "s3", then it must be
	// "s3-something".
	if !strings.HasPrefix(regionPart, "s3-") {
		return "", errors.New("Invalid action URL. " +
			`Second word in period-separated host is not "s3", and does not start with "s3-".`)
	}

	return regionPart[len("s3-"):], nil
}

// tokenOfUploaderURL parses an uploader URL string, and returns its token
// parameter.
//
// Example:
//         const u = "https://uploader.synq.fm/uploader/" +
//         "00000000000000000000000000000000" +
//         "?token=11111111111111111111111111111111"
//
//         token, err := tokenOfUploaderURL(u)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(token) // prints 11111111111111111111111111111111
func tokenOfUploaderURL(uploaderURL string) (string, error) {
	u, err := url.Parse(uploaderURL)
	if err != nil {
		return "", err
	}

	values, err := url.ParseQuery(u.RawQuery)
	if err != nil {
		return "", err
	}

	token := values.Get("token")
	if token == "" {
		return "", errors.New("Found no token parameter in URL.")
	}

	return token, nil
}

// MultipartUploadSigner returns a function that can be added to an s3 client's
// list of handlers. The function will take over signing of requests from
// aws-sdk-go.
//
// The signer function uses SYNQ's embeddable web uploader's remote procedure
// to sign requests.
//
// This function is used by the internal multipartUpload function.
//
// Example:
//         // AWS session.
//         sess := session.Must(session.NewSession())
//
//         // S3 service client.
//         svc := s3.New(sess)
//
//         // Signer function. Determine the arguments somehow.
//         signer := MultipartUploadSigner(acl, awsAccessKeyId, bucket, contentType, key, token, video_id)
//
//         // Register handler as the last handler of the signing phase.
//         svc.Handlers.Sign.PushBack(signer)
//
//         // S3 requests are now signed by signer().
func MultipartUploadSigner(acl, awsAccessKeyId, bucket, contentType, key, token, video_id string) func(r *request.Request) {
	signer := func(r *request.Request) {
		hr := r.HTTPRequest

		// rewrite the X-Amz-Date header into the format that
		// https://uploader.synq.fm/uploader/signature expects
		{
			x_amz_date_in := hr.Header.Get("X-Amz-Date")
			if x_amz_date_in == "" {
				return // TODO(mastensg): how to report errors from handlers?
			}
			x_amz_date_t, err := time.Parse("20060102T150405Z", x_amz_date_in)
			if err != nil {
				return // TODO(mastensg): how to report errors from handlers?
			}
			x_amz_date := x_amz_date_t.Format("Mon, 2 Jan 2006 15:04:05 MST")
			delete(hr.Header, "X-Amz-Date") // TODO(mastensg): enough to just set and not delete?
			hr.Header.Set("X-Amz-Date", x_amz_date)
		}

		x_amz_date := hr.Header.Get("X-Amz-Date")

		// construct "headers" string to send to
		// https://uploader.synq.fm/uploader/signature
		headers := ""
		if hr.URL.RawQuery == "uploads=" {
			// Initiate multi-part upload

			headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-acl:%s\nx-amz-date:%s\n/%s%s",
				hr.Method,
				contentType,
				acl,
				x_amz_date,
				bucket,
				hr.URL.Path+"?uploads",
			)
		} else if hr.Method == "PUT" {
			// Upload one part

			headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
				hr.Method,
				"",
				x_amz_date,
				bucket,
				hr.URL.Path+"?"+hr.URL.RawQuery,
			)
		} else if hr.Method == "POST" {
			// Finish multi-part upload

			// TODO(mastensg): parameterize content-type(?)
			headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
				hr.Method,
				"application/xml; charset=UTF-8",
				x_amz_date,
				bucket,
				hr.URL.Path+"?"+hr.URL.RawQuery,
			)

			// TODO(mastensg): the content-type header set by
			// aws-sdk-go is not exactly the one expected by
			// uploader/signature, maybe
			hr.Header.Set("Content-Type", "application/xml; charset=UTF-8")
		} else {
			// Unknown request type
			return // TODO(mastensg): how to report errors from handlers?
		}

		signature, err := UploaderSignature(UploaderSignatureUrlFormat, video_id, token, headers)
		if err != nil {
			return // TODO(mastensg): how to report errors from handlers?
		}

		// rewrite authorization header(s)
		delete(hr.Header, "X-Amz-Content-Sha256") // TODO(mastensg): can this be left in?
		delete(hr.Header, "Authorization")
		authorization := fmt.Sprintf("AWS %s:%s", awsAccessKeyId, signature)
		hr.Header.Set("Authorization", authorization)
	}

	return signer
}

// multipartUpload uploads a file as the video's original_file.
// This procedure will use Amazon S3's Multipart Upload API:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
//
// This is the internal function to make uploads, which is called by the public
// MultipartUpload. This function uses s3manager from aws-sdk-go to manage the
// process of uploading in multiple parts. In particular, this will start
// several goroutines that will upload parts concurrently.
func multipartUpload(body io.Reader, acl, actionURL, awsAccessKeyId, contentType, key, uploaderURL, video_id string) (*s3manager.UploadOutput, error) {
	bucket, err := bucketOfUploadAction(actionURL)
	if err != nil {
		return nil, err
	}

	region, err := regionOfUploadAction(actionURL)
	if err != nil {
		return nil, err
	}

	token, err := tokenOfUploaderURL(uploaderURL)
	if err != nil {
		return nil, err
	}

	// credentials
	provider := credentials.StaticProvider{}
	provider.Value.AccessKeyID = multipartUploadAwsAccessKeyId
	provider.Value.SecretAccessKey = multipartUploadAwsSecretAccessKey
	credentials := credentials.NewCredentials(&provider)

	// session
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials,
		Region:      aws.String(region),
	})
	if err != nil {
		return nil, err
	}

	svc := s3.New(sess)

	// sign handler
	signer := MultipartUploadSigner(acl, awsAccessKeyId, bucket, contentType, key, token, video_id)
	svc.Handlers.Sign.PushBack(signer)

	// s3manager uploader
	uploader := s3manager.NewUploaderWithClient(svc)

	// upload parameters
	uploadInput := &s3manager.UploadInput{
		ACL:         &acl,
		Body:        body,
		Bucket:      &bucket,
		ContentType: &contentType,
		Key:         &key,
	}

	return uploader.Upload(uploadInput)
}
<commit_msg>synq/multipart_upload: define one content type once<commit_after>package synq

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"
)

// These Amazon Web Services credentials are provided to the AWS SDK, which is
// used to upload content in multiple parts. There is no IAM user with these
// credentials; they are supplied because the AWS SDK requires some credentials
// to attempt to start uploading. This package replaces the AWS SDK's request
// signing method with its own method.
const multipartUploadAwsAccessKeyId = "AAAAAAAAAAAAAAAAAAAA"
const multipartUploadAwsSecretAccessKey = "ssssssssssssssssssssssssssssssssssssssss"

// UploaderSignatureUrlFormat is a printf format string which is used when
// signing multipart upload requests.
// TODO(mastensg): Determine this format (or at least prefix) at runtime, from
// the Synq HTTP API.
const UploaderSignatureUrlFormat = "https://uploader.synq.fm/uploader/signature/%s?token=%s"

// UploaderSignatureRequest is the request that is sent when using the
// embeddable web uploader's request signing service.
type UploaderSignatureRequest struct {
	Headers string `json:"headers"`
}

// UploaderSignatureResponse is the response that is received when using the
// embeddable web uploader's request signing service.
type UploaderSignatureResponse struct {
	Signature string `json:"signature"`
}

// UploaderSignature uses the backend of the embeddable web uploader to sign
// multipart upload requests.
func UploaderSignature(url_fmt, video_id, token, headers string) ([]byte, error) {
	url := fmt.Sprintf(url_fmt, video_id, token)

	// construct request body
	reqStruct := UploaderSignatureRequest{Headers: headers}
	reqBody, err := json.Marshal(reqStruct)
	if err != nil {
		return nil, err
	}

	// perform request
	resp, err := http.Post(url, "application/json", bytes.NewReader(reqBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// read response
	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	// parse response
	respStruct := UploaderSignatureResponse{}
	err = json.Unmarshal(respBody, &respStruct)
	if err != nil {
		return nil, err
	}

	return []byte(respStruct.Signature), nil
}

// bucketOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the bucket name part of that URL.
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         bucket, err := bucketOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(bucket) // prints synqfm
func bucketOfUploadAction(actionURL string) (string, error) {
	u, err := url.Parse(actionURL)
	if err != nil {
		return "", err
	}

	hs := strings.Split(u.Host, ".")
	if len(hs) != 4 {
		return "", errors.New("Invalid action URL. " +
			"Not exactly 4 period-separated words in host.")
	}
	if !strings.HasPrefix(hs[1], "s3") {
		return "", errors.New("Invalid action URL. " +
			"Second word in period-separated host does not start with s3")
	}
	if hs[2] != "amazonaws" {
		return "", errors.New("Invalid action URL. " +
			"Third word in period-separated host is not amazonaws")
	}
	if hs[3] != "com" {
		return "", errors.New("Invalid action URL. " +
			"Fourth word in period-separated host is not com")
	}

	return hs[0], nil
}

// regionOfUploadAction parses an "action" URL as received with GetUploadInfo,
// and returns the region of the bucket that is the URL refers to.
//
// This relies on heuristics, and will not work with certain styles of URLs.
//
// See: http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
//
// Example:
//         const a = "https://synqfm.s3.amazonaws.com"
//
//         region, err := regionOfUploadAction(a)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(region) // prints us-east-1
func regionOfUploadAction(actionURL string) (string, error) {
	u, err := url.Parse(actionURL)
	if err != nil {
		return "", err
	}

	hs := strings.Split(u.Host, ".")
	if len(hs) != 4 {
		return "", errors.New("Invalid action URL. " +
			"Not exactly 4 period-separated words in host.")
	}
	if !strings.HasPrefix(hs[1], "s3") {
		return "", errors.New("Invalid action URL. " +
			"Second word in period-separated host does not start with s3")
	}
	if hs[2] != "amazonaws" {
		return "", errors.New("Invalid action URL. " +
			"Third word in period-separated host is not amazonaws")
	}
	if hs[3] != "com" {
		return "", errors.New("Invalid action URL. " +
			"Fourth word in period-separated host is not com")
	}

	regionPart := hs[1]

	// us-east-1 is the region if nothing else is specified.
	if regionPart == "s3" {
		return "us-east-1", nil
	}

	// If the region part of the host is not exactly "s3", then it must be
	// "s3-something".
	if !strings.HasPrefix(regionPart, "s3-") {
		return "", errors.New("Invalid action URL. " +
			`Second word in period-separated host is not "s3", and does not start with "s3-".`)
	}

	return regionPart[len("s3-"):], nil
}

// tokenOfUploaderURL parses an uploader URL string, and returns its token
// parameter.
//
// Example:
//         const u = "https://uploader.synq.fm/uploader/" +
//         "00000000000000000000000000000000" +
//         "?token=11111111111111111111111111111111"
//
//         token, err := tokenOfUploaderURL(u)
//         if err != nil {
//                 log.Fatal(err)
//         }
//         fmt.Println(token) // prints 11111111111111111111111111111111
func tokenOfUploaderURL(uploaderURL string) (string, error) {
	u, err := url.Parse(uploaderURL)
	if err != nil {
		return "", err
	}

	values, err := url.ParseQuery(u.RawQuery)
	if err != nil {
		return "", err
	}

	token := values.Get("token")
	if token == "" {
		return "", errors.New("Found no token parameter in URL.")
	}

	return token, nil
}

// MultipartUploadSigner returns a function that can be added to an s3 client's
// list of handlers. The function will take over signing of requests from
// aws-sdk-go.
//
// The signer function uses SYNQ's embeddable web uploader's remote procedure
// to sign requests.
//
// This function is used by the internal multipartUpload function.
//
// Example:
//         // AWS session.
//         sess := session.Must(session.NewSession())
//
//         // S3 service client.
//         svc := s3.New(sess)
//
//         // Signer function. Determine the arguments somehow.
//         signer := MultipartUploadSigner(acl, awsAccessKeyId, bucket, contentType, key, token, video_id)
//
//         // Register handler as the last handler of the signing phase.
//         svc.Handlers.Sign.PushBack(signer)
//
//         // S3 requests are now signed by signer().
func MultipartUploadSigner(acl, awsAccessKeyId, bucket, contentType, key, token, video_id string) func(r *request.Request) {
	signer := func(r *request.Request) {
		hr := r.HTTPRequest

		// rewrite the X-Amz-Date header into the format that
		// https://uploader.synq.fm/uploader/signature expects
		{
			x_amz_date_in := hr.Header.Get("X-Amz-Date")
			if x_amz_date_in == "" {
				return // TODO(mastensg): how to report errors from handlers?
			}
			x_amz_date_t, err := time.Parse("20060102T150405Z", x_amz_date_in)
			if err != nil {
				return // TODO(mastensg): how to report errors from handlers?
			}
			x_amz_date := x_amz_date_t.Format("Mon, 2 Jan 2006 15:04:05 MST")
			delete(hr.Header, "X-Amz-Date") // TODO(mastensg): enough to just set and not delete?
			hr.Header.Set("X-Amz-Date", x_amz_date)
		}

		x_amz_date := hr.Header.Get("X-Amz-Date")

		// construct "headers" string to send to
		// https://uploader.synq.fm/uploader/signature
		headers := ""
		if hr.URL.RawQuery == "uploads=" {
			// Initiate multi-part upload

			headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-acl:%s\nx-amz-date:%s\n/%s%s",
				hr.Method,
				contentType,
				acl,
				x_amz_date,
				bucket,
				hr.URL.Path+"?uploads",
			)
		} else if hr.Method == "PUT" {
			// Upload one part

			headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
				hr.Method,
				"",
				x_amz_date,
				bucket,
				hr.URL.Path+"?"+hr.URL.RawQuery,
			)
		} else if hr.Method == "POST" {
			// Finish multi-part upload

			// TODO(mastensg): the content-type header set by
			// aws-sdk-go is not exactly the one expected by
			// uploader/signature, maybe
			hr.Header.Set("Content-Type", "application/xml; charset=UTF-8")

			headers = fmt.Sprintf("%s\n\n%s\n\nx-amz-date:%s\n/%s%s",
				hr.Method,
				hr.Header.Get("Content-Type"),
				x_amz_date,
				bucket,
				hr.URL.Path+"?"+hr.URL.RawQuery,
			)
		} else {
			// Unknown request type
			return // TODO(mastensg): how to report errors from handlers?
		}

		signature, err := UploaderSignature(UploaderSignatureUrlFormat, video_id, token, headers)
		if err != nil {
			return // TODO(mastensg): how to report errors from handlers?
		}

		// rewrite authorization header(s)
		delete(hr.Header, "X-Amz-Content-Sha256") // TODO(mastensg): can this be left in?
		delete(hr.Header, "Authorization")
		authorization := fmt.Sprintf("AWS %s:%s", awsAccessKeyId, signature)
		hr.Header.Set("Authorization", authorization)
	}

	return signer
}

// multipartUpload uploads a file as the video's original_file.
// This procedure will use Amazon S3's Multipart Upload API:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
//
// This is the internal function to make uploads, which is called by the public
// MultipartUpload. This function uses s3manager from aws-sdk-go to manage the
// process of uploading in multiple parts. In particular, this will start
// several goroutines that will upload parts concurrently.
func multipartUpload(body io.Reader, acl, actionURL, awsAccessKeyId, contentType, key, uploaderURL, video_id string) (*s3manager.UploadOutput, error) {
	bucket, err := bucketOfUploadAction(actionURL)
	if err != nil {
		return nil, err
	}

	region, err := regionOfUploadAction(actionURL)
	if err != nil {
		return nil, err
	}

	token, err := tokenOfUploaderURL(uploaderURL)
	if err != nil {
		return nil, err
	}

	// credentials
	provider := credentials.StaticProvider{}
	provider.Value.AccessKeyID = multipartUploadAwsAccessKeyId
	provider.Value.SecretAccessKey = multipartUploadAwsSecretAccessKey
	credentials := credentials.NewCredentials(&provider)

	// session
	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials,
		Region:      aws.String(region),
	})
	if err != nil {
		return nil, err
	}

	svc := s3.New(sess)

	// sign handler
	signer := MultipartUploadSigner(acl, awsAccessKeyId, bucket, contentType, key, token, video_id)
	svc.Handlers.Sign.PushBack(signer)

	// s3manager uploader
	uploader := s3manager.NewUploaderWithClient(svc)

	// upload parameters
	uploadInput := &s3manager.UploadInput{
		ACL:         &acl,
		Body:        body,
		Bucket:      &bucket,
		ContentType: &contentType,
		Key:         &key,
	}

	return uploader.Upload(uploadInput)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// +build go1.3

package lxdclient

import (
	"os"
	"path"
	"path/filepath"

	"github.com/juju/errors"
	"github.com/lxc/lxd"
)

// The LXD repo does not expose any consts for these values.
// TODO(ericsnow) Expose these consts in the LXD repo?
const (
	// see https://github.com/lxc/lxd/blob/master/config.go
	configDefaultFile = "config.yml"
	// see https://github.com/lxc/lxd/blob/master/client.go (readMyCert)
	configCertFile = "client.crt"
	configKeyFile  = "client.key"
)

// Config contains the config values used for a connection to the LXD API.
type Config struct {
	// Namespace identifies the namespace to associate with containers
	// and other resources with which the client interacts. If may be
	// blank.
	Namespace string

	// Dirname identifies where the client will find config files.
	// default: "$HOME/.config/lxc"
	Dirname string

	// Remote identifies the remote server to which the client should
	// connect. For the default "remote" use Local.
	Remote Remote
}

// ConfigPath returns the full path to Juju LXC config directory.
// This is here so we do not have to import lxc/lxd into provider.
func ConfigPath(namespace string) string {
	return lxd.ConfigPath(namespace)
}

// WithDefaults updates a copy of the config with default values
// where needed.
func (cfg Config) WithDefaults() (Config, error) {
	// We leave a blank namespace alone.
	// Also, note that cfg is a value receiver, so it is an implicit copy.

	if cfg.Dirname == "" {
		// TODO(ericsnow) Switch to filepath as soon as LXD does.
		dirname := path.Dir(lxd.ConfigPath("DUMMY"))
		cfg.Dirname = path.Clean(dirname)
	}

	var err error
	cfg.Remote, err = cfg.Remote.WithDefaults()
	if err != nil {
		return cfg, errors.Trace(err)
	}

	return cfg, nil
}

// Validate checks the client's fields for invalid values.
func (cfg Config) Validate() error {
	// TODO(ericsnow) Check cfg.Namespace (if provided)?

	// TODO(ericsnow) Check cfg.Dirname (if provided)?

	// TODO(ericsnow) Check cfg.Filename (if provided)?

	if err := cfg.Remote.Validate(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// Write writes all the various files for this config.
func (cfg Config) Write() error {
	if err := cfg.Validate(); err != nil {
		return errors.Trace(err)
	}

	origConfigDir := updateLXDVars(cfg.Dirname)
	defer updateLXDVars(origConfigDir)

	if err := cfg.write(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// UsingTCPRemote converts the config into a "non-local" version. An
// already non-local remote is left alone.
//
// For a "local" remote (see Local), the remote is changed to a one
// with the host set to the IP address of the local lxcbr0 bridge
// interface. The LXD server is also set up for remote access, exposing
// the TCP port and adding a certificate for remote access.
func (cfg Config) UsingTCPRemote() (Config, error) {
	// Note that cfg is a value receiver, so it is an implicit copy.

	if !cfg.Remote.isLocal() {
		return cfg, nil
	}

	remote, err := cfg.Remote.UsingTCP()
	if err != nil {
		return cfg, errors.Trace(err)
	}

	// Update the server config and authorized certs.
	if err := prepareRemote(cfg, *remote.Cert); err != nil {
		return cfg, errors.Trace(err)
	}

	cfg.Remote = remote
	return cfg, nil
}

func prepareRemote(cfg Config, newCert Cert) error {
	client, err := Connect(cfg)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.SetConfig("core.https_address", "[::]"); err != nil {
		return errors.Trace(err)
	}

	if err := client.AddCert(newCert); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func updateLXDVars(dirname string) string {
	// Change the hard-coded config dir that the raw client uses.
	// TODO(ericsnow) This is exactly what happens in the lxc CLI for
	// the LXD_CONF env var. Once the raw client accepts a path to the
	// config dir we can drop this line.
	// See:
	//   https://github.com/lxc/lxd/blob/master/lxc/main.go
	//   https://github.com/lxc/lxd/issues/1196
	origConfigDir := lxd.ConfigDir
	lxd.ConfigDir = dirname

	return origConfigDir
}

//TODO(wwitzel3) make sure this is idempotent
func initializeConfigDir(cfg Config) error {
	logger.Debugf("initializing config dir %q", cfg.Dirname)

	if err := os.MkdirAll(cfg.Dirname, 0775); err != nil {
		return errors.Trace(err)
	}

	// Force the default config to get written. on
	// default config from memory if there isn't a config file on disk.
	// So we load that and then explicitly save it to disk with a call
	// to SaveConfig().
	config, err := lxd.LoadConfig()
	if err != nil {
		return errors.Trace(err)
	}

	if err := lxd.SaveConfig(config); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (cfg Config) write() error {
	// Ensure the initial config is set up.
	if err := initializeConfigDir(cfg); err != nil {
		return errors.Trace(err)
	}

	// Update config.yml, if necessary.
	if err := cfg.writeConfigFile(); err != nil {
		return errors.Trace(err)
	}

	// Write the cert file and key file, if applicable.
	var cert Cert
	if cfg.Remote.Cert != nil {
		cert = *cfg.Remote.Cert
	}
	if err := cert.Validate(); err != nil {
		logger.Debugf("not writing invalid/empty certificate")
	} else {
		if err := cfg.writeCertPEM(cert); err != nil {
			return errors.Trace(err)
		}
		if err := cfg.writeKeyPEM(cert); err != nil {
			return errors.Trace(err)
		}
	}

	return nil
}

func (cfg Config) writeConfigFile() error {
	filename := cfg.resolve(configDefaultFile)
	logger.Debugf("writing config file %q", filename)

	// TODO(ericsnow) Cache the low-level config in Config?
	rawCfg, err := lxd.LoadConfig()
	if err != nil {
		return errors.Trace(err)
	}

	if !cfg.Remote.isLocal() {
		// Ensure the remote is set correctly.
		remote := cfg.Remote.Name
		delete(rawCfg.Remotes, remote)
		addr := cfg.Remote.Host
		if err := addServer(rawCfg, remote, addr); err != nil {
			return errors.Trace(err)
		}
	}

	// Write out the updated config, if changed.
	// TODO(ericsnow) Check if changed.
	if err := lxd.SaveConfig(rawCfg); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (cfg Config) writeCertPEM(cert Cert) error {
	filename := cfg.resolve(configCertFile)
	logger.Debugf("writing cert PEM file %q", filename)

	file, err := os.Create(filename)
	if err != nil {
		return errors.Trace(err)
	}
	defer file.Close()

	if err := cert.WriteCertPEM(file); err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (cfg Config) writeKeyPEM(cert Cert) error {
	filename := cfg.resolve(configKeyFile)
	logger.Debugf("writing key PEM file %q", filename)

	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return errors.Trace(err)
	}
	defer file.Close()

	if err := cert.WriteKeyPEM(file); err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (cfg Config) resolve(file string) string {
	return filepath.Join(cfg.Dirname, file)
}
<commit_msg>updating comments<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

// +build go1.3

package lxdclient

import (
	"os"
	"path"
	"path/filepath"

	"github.com/juju/errors"
	"github.com/lxc/lxd"
)

// The LXD repo does not expose any consts for these values.
// TODO(ericsnow) Expose these consts in the LXD repo?
const (
	// see https://github.com/lxc/lxd/blob/master/config.go
	configDefaultFile = "config.yml"
	// see https://github.com/lxc/lxd/blob/master/client.go (readMyCert)
	configCertFile = "client.crt"
	configKeyFile  = "client.key"
)

// Config contains the config values used for a connection to the LXD API.
type Config struct {
	// Namespace identifies the namespace to associate with containers
	// and other resources with which the client interacts. If may be
	// blank.
	Namespace string

	// Dirname identifies where the client will find config files.
	// default: "$HOME/.config/lxc"
	Dirname string

	// Remote identifies the remote server to which the client should
	// connect. For the default "remote" use Local.
	Remote Remote
}

// ConfigPath returns the full path to Juju LXC config directory.
// This is here so we do not have to import lxc/lxd into the provider.
func ConfigPath(namespace string) string {
	return lxd.ConfigPath(namespace)
}

// WithDefaults updates a copy of the config with default values
// where needed.
func (cfg Config) WithDefaults() (Config, error) {
	// We leave a blank namespace alone.
	// Also, note that cfg is a value receiver, so it is an implicit copy.

	if cfg.Dirname == "" {
		// TODO(ericsnow) Switch to filepath as soon as LXD does.
		dirname := path.Dir(lxd.ConfigPath("DUMMY"))
		cfg.Dirname = path.Clean(dirname)
	}

	var err error
	cfg.Remote, err = cfg.Remote.WithDefaults()
	if err != nil {
		return cfg, errors.Trace(err)
	}

	return cfg, nil
}

// Validate checks the client's fields for invalid values.
func (cfg Config) Validate() error {
	// TODO(ericsnow) Check cfg.Namespace (if provided)?

	// TODO(ericsnow) Check cfg.Dirname (if provided)?

	// TODO(ericsnow) Check cfg.Filename (if provided)?

	if err := cfg.Remote.Validate(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// Write writes all the various files for this config.
func (cfg Config) Write() error {
	if err := cfg.Validate(); err != nil {
		return errors.Trace(err)
	}

	origConfigDir := updateLXDVars(cfg.Dirname)
	defer updateLXDVars(origConfigDir)

	if err := cfg.write(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// UsingTCPRemote converts the config into a "non-local" version. An
// already non-local remote is left alone.
//
// For a "local" remote (see Local), the remote is changed to a one
// with the host set to the IP address of the local lxcbr0 bridge
// interface. The LXD server is also set up for remote access, exposing
// the TCP port and adding a certificate for remote access.
func (cfg Config) UsingTCPRemote() (Config, error) {
	// Note that cfg is a value receiver, so it is an implicit copy.

	if !cfg.Remote.isLocal() {
		return cfg, nil
	}

	remote, err := cfg.Remote.UsingTCP()
	if err != nil {
		return cfg, errors.Trace(err)
	}

	// Update the server config and authorized certs.
	if err := prepareRemote(cfg, *remote.Cert); err != nil {
		return cfg, errors.Trace(err)
	}

	cfg.Remote = remote
	return cfg, nil
}

func prepareRemote(cfg Config, newCert Cert) error {
	client, err := Connect(cfg)
	if err != nil {
		return errors.Trace(err)
	}

	if err := client.SetConfig("core.https_address", "[::]"); err != nil {
		return errors.Trace(err)
	}

	if err := client.AddCert(newCert); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func updateLXDVars(dirname string) string {
	// Change the hard-coded config dir that the raw client uses.
	// TODO(ericsnow) This is exactly what happens in the lxc CLI for
	// the LXD_CONF env var. Once the raw client accepts a path to the
	// config dir we can drop this line.
	// See:
	//   https://github.com/lxc/lxd/blob/master/lxc/main.go
	//   https://github.com/lxc/lxd/issues/1196
	origConfigDir := lxd.ConfigDir
	lxd.ConfigDir = dirname

	return origConfigDir
}

//TODO(wwitzel3) make sure this is idempotent
func initializeConfigDir(cfg Config) error {
	logger.Debugf("initializing config dir %q", cfg.Dirname)

	if err := os.MkdirAll(cfg.Dirname, 0775); err != nil {
		return errors.Trace(err)
	}

	// Force the default config to get written. LoadConfig() returns the
	// default config from memory if there isn't a config file on disk.
	// So we load that and then explicitly save it to disk with a call
	// to SaveConfig().
	config, err := lxd.LoadConfig()
	if err != nil {
		return errors.Trace(err)
	}

	if err := lxd.SaveConfig(config); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (cfg Config) write() error {
	// Ensure the initial config is set up.
	if err := initializeConfigDir(cfg); err != nil {
		return errors.Trace(err)
	}

	// Update config.yml, if necessary.
	if err := cfg.writeConfigFile(); err != nil {
		return errors.Trace(err)
	}

	// Write the cert file and key file, if applicable.
	var cert Cert
	if cfg.Remote.Cert != nil {
		cert = *cfg.Remote.Cert
	}
	if err := cert.Validate(); err != nil {
		logger.Debugf("not writing invalid/empty certificate")
	} else {
		if err := cfg.writeCertPEM(cert); err != nil {
			return errors.Trace(err)
		}
		if err := cfg.writeKeyPEM(cert); err != nil {
			return errors.Trace(err)
		}
	}

	return nil
}

func (cfg Config) writeConfigFile() error {
	filename := cfg.resolve(configDefaultFile)
	logger.Debugf("writing config file %q", filename)

	// TODO(ericsnow) Cache the low-level config in Config?
	rawCfg, err := lxd.LoadConfig()
	if err != nil {
		return errors.Trace(err)
	}

	if !cfg.Remote.isLocal() {
		// Ensure the remote is set correctly.
		remote := cfg.Remote.Name
		delete(rawCfg.Remotes, remote)
		addr := cfg.Remote.Host
		if err := addServer(rawCfg, remote, addr); err != nil {
			return errors.Trace(err)
		}
	}

	// Write out the updated config, if changed.
	// TODO(ericsnow) Check if changed.
	if err := lxd.SaveConfig(rawCfg); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (cfg Config) writeCertPEM(cert Cert) error {
	filename := cfg.resolve(configCertFile)
	logger.Debugf("writing cert PEM file %q", filename)

	file, err := os.Create(filename)
	if err != nil {
		return errors.Trace(err)
	}
	defer file.Close()

	if err := cert.WriteCertPEM(file); err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (cfg Config) writeKeyPEM(cert Cert) error {
	filename := cfg.resolve(configKeyFile)
	logger.Debugf("writing key PEM file %q", filename)

	file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return errors.Trace(err)
	}
	defer file.Close()

	if err := cert.WriteKeyPEM(file); err != nil {
		return errors.Trace(err)
	}
	return nil
}

func (cfg Config) resolve(file string) string {
	return filepath.Join(cfg.Dirname, file)
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxdclient

import (
	"github.com/juju/errors"
)

const (
	remoteLocalName   = "local"
	remoteDefaultName = remoteLocalName

	// TODO(ericsnow) This may be changing to "local"
	remoteIDForLocal = ""
)

// Local is LXD's default "remote". Essentially it is an unencrypted,
// unauthenticated connection to localhost over a unix socket.
var Local = NewRemote(RemoteInfo{
	Name: remoteLocalName,
})

// RemoteInfo describes a LXD "remote" server for a client. In
// particular it holds the information needed for the client
// to connect to the remote.
type RemoteInfo struct {
	// Name is a label for this remote.
	Name string

	// Host identifies the host to which the client should connect.
	// An empty string is interpreted as:
	//   "localhost over a unix socket (unencrypted)".
	Host string

	// Cert holds the TLS certificate data for the client to use.
	Cert *Certificate
}

// SetDefaults updates a copy of the remote with default values
// where needed.
func (ri RemoteInfo) SetDefaults() (RemoteInfo, error) {
	if ri.Host == "" {
		return ri.setLocalDefaults(), nil
	}

	if ri.Cert == nil {
		cert, err := GenerateCertificate(genCertAndKey)
		if err != nil {
			return ri, errors.Trace(err)
		}
		ri.Cert = cert
	}

	return ri, nil
}

func (ri RemoteInfo) setLocalDefaults() RemoteInfo {
	if ri.Name == "" {
		ri.Name = remoteLocalName
	}

	// TODO(ericsnow) Set ri.Cert to nil?

	return ri
}

// Validate checks the RemoteInfo fields for invalid values.
func (ri RemoteInfo) Validate() error {
	if ri.Name == "" {
		return errors.NotValidf("remote missing name,")
	}

	if ri.Host == "" {
		if err := ri.validateLocal(); err != nil {
			return errors.Trace(err)
		}
		return nil
	}

	// TODO(ericsnow) Ensure the host is a valid hostname or address?

	if ri.Cert == nil {
		return errors.NotValidf("remote without cert")
	} else if err := ri.Cert.Validate(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (ri RemoteInfo) validateLocal() error {
	if ri.Cert != nil {
		return errors.NotValidf("hostless remote with cert")
	}

	return nil
}

// Remote represents a single LXD remote server.
type Remote struct {
	info RemoteInfo
}

// NewRemote builds a new Remote from the provided info.
func NewRemote(info RemoteInfo) Remote {
	return Remote{
		info: info,
	}
}

// setDefaults updates a copy of the remote with default values
// where needed.
func (r Remote) setDefaults() (Remote, error) {
	info, err := r.info.SetDefaults()
	if err != nil {
		return r, errors.Trace(err)
	}
	r.info = info

	return r, nil
}

// Validate ensures that the Remote is valid.
func (r Remote) Validate() error {
	if err := r.info.Validate(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

// ID identifies the remote to the raw LXD client code.
func (r Remote) ID() string {
	if r.info.Host == "" {
		return remoteIDForLocal
	}
	return r.info.Name
}

// Cert returns the certificate the client should use.
func (r Remote) Cert() Certificate {
	if r.info.Cert == nil {
		return Certificate{}
	}
	return *r.info.Cert
}

// TODO(ericsnow) Add a "Connect(Config)" method that connects
// to the remote and returns the corresponding Client.

// TODO(ericsnow) Add a "Register" method to Client that adds the remote
// to the client's remote?
<commit_msg>Partially fold RemoteInfo into Remote.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package lxdclient

import (
	"github.com/juju/errors"
)

const (
	remoteLocalName   = "local"
	remoteDefaultName = remoteLocalName

	// TODO(ericsnow) This may be changing to "local"
	remoteIDForLocal = ""
)

// Local is LXD's default "remote". Essentially it is an unencrypted,
// unauthenticated connection to localhost over a unix socket.
var Local = Remote{
	RemoteInfo: RemoteInfo{
		Name: remoteLocalName,
	},
}

// TODO(ericsnow) Fold RemoteInfo into Remote.

// RemoteInfo describes a LXD "remote" server for a client. In
// particular it holds the information needed for the client
// to connect to the remote.
type RemoteInfo struct {
	// Name is a label for this remote.
	Name string

	// Host identifies the host to which the client should connect.
	// An empty string is interpreted as:
	//   "localhost over a unix socket (unencrypted)".
	Host string

	// Cert holds the TLS certificate data for the client to use.
	Cert *Certificate
}

// SetDefaults updates a copy of the remote with default values
// where needed.
func (ri RemoteInfo) SetDefaults() (RemoteInfo, error) {
	if ri.Host == "" {
		return ri.setLocalDefaults(), nil
	}

	if ri.Cert == nil {
		cert, err := GenerateCertificate(genCertAndKey)
		if err != nil {
			return ri, errors.Trace(err)
		}
		ri.Cert = cert
	}

	return ri, nil
}

func (ri RemoteInfo) setLocalDefaults() RemoteInfo {
	if ri.Name == "" {
		ri.Name = remoteLocalName
	}

	// TODO(ericsnow) Set ri.Cert to nil?

	return ri
}

// Validate checks the RemoteInfo fields for invalid values.
func (ri RemoteInfo) Validate() error {
	if ri.Name == "" {
		return errors.NotValidf("remote missing name,")
	}

	if ri.Host == "" {
		if err := ri.validateLocal(); err != nil {
			return errors.Trace(err)
		}
		return nil
	}

	// TODO(ericsnow) Ensure the host is a valid hostname or address?

	if ri.Cert == nil {
		return errors.NotValidf("remote without cert")
	} else if err := ri.Cert.Validate(); err != nil {
		return errors.Trace(err)
	}

	return nil
}

func (ri RemoteInfo) validateLocal() error {
	if ri.Cert != nil {
		return errors.NotValidf("hostless remote with cert")
	}

	return nil
}

// Remote represents a single LXD remote server.
type Remote struct {
	RemoteInfo
}

// NewRemote builds a new Remote from the provided info.
func NewRemote(info RemoteInfo) Remote {
	return Remote{
		RemoteInfo: info,
	}
}

// setDefaults updates a copy of the remote with default values
// where needed.
func (r Remote) setDefaults() (Remote, error) {
	info, err := r.RemoteInfo.SetDefaults()
	if err != nil {
		return r, errors.Trace(err)
	}
	r.RemoteInfo = info

	return r, nil
}

// ID identifies the remote to the raw LXD client code.
func (r Remote) ID() string {
	if r.RemoteInfo.Host == "" {
		return remoteIDForLocal
	}
	return r.RemoteInfo.Name
}

// Cert returns the certificate the client should use.
func (r Remote) Cert() Certificate {
	if r.RemoteInfo.Cert == nil {
		return Certificate{}
	}
	return *r.RemoteInfo.Cert
}

// TODO(ericsnow) Add a "Connect(Config)" method that connects
// to the remote and returns the corresponding Client.

// TODO(ericsnow) Add a "Register" method to Client that adds the remote
// to the client's remote?
<|endoftext|>


<commit_before>package controllers

import (
	"compress/gzip"
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/NYTimes/gziphandler"
	"github.com/gophish/gophish/config"
	ctx "github.com/gophish/gophish/context"
	"github.com/gophish/gophish/controllers/api"
	log "github.com/gophish/gophish/logger"
	"github.com/gophish/gophish/models"
	"github.com/gophish/gophish/util"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/jordan-wright/unindexed"
)

// ErrInvalidRequest is thrown when a request with an invalid structure is
// received
var ErrInvalidRequest = errors.New("Invalid request")

// ErrCampaignComplete is thrown when an event is received for a campaign that
// has already been marked as complete.
var ErrCampaignComplete = errors.New("Event received on completed campaign")

// TransparencyResponse is the JSON response provided when a third-party
// makes a request to the transparency handler.
type TransparencyResponse struct {
	Server         string    `json:"server"`
	ContactAddress string    `json:"contact_address"`
	SendDate       time.Time `json:"send_date"`
}

// TransparencySuffix (when appended to a valid result ID), will cause Gophish
// to return a transparency response.
const TransparencySuffix = "+"

// PhishingServerOption is a functional option that is used to configure the
// the phishing server
type PhishingServerOption func(*PhishingServer)

// PhishingServer is an HTTP server that implements the campaign event
// handlers, such as email open tracking, click tracking, and more.
type PhishingServer struct {
	server         *http.Server
	config         config.PhishServer
	contactAddress string
}

// NewPhishingServer returns a new instance of the phishing server with
// provided options applied.
func NewPhishingServer(config config.PhishServer, options ...PhishingServerOption) *PhishingServer {
	defaultServer := &http.Server{
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		Addr:         config.ListenURL,
	}
	ps := &PhishingServer{
		server: defaultServer,
		config: config,
	}
	for _, opt := range options {
		opt(ps)
	}
	ps.registerRoutes()
	return ps
}

// WithContactAddress sets the contact address used by the transparency
// handlers
func WithContactAddress(addr string) PhishingServerOption {
	return func(ps *PhishingServer) {
		ps.contactAddress = addr
	}
}

// Start launches the phishing server, listening on the configured address.
func (ps *PhishingServer) Start() error {
	if ps.config.UseTLS {
		err := util.CheckAndCreateSSL(ps.config.CertPath, ps.config.KeyPath)
		if err != nil {
			log.Fatal(err)
			return err
		}
		log.Infof("Starting phishing server at https://%s", ps.config.ListenURL)
		return ps.server.ListenAndServeTLS(ps.config.CertPath, ps.config.KeyPath)
	}
	// If TLS isn't configured, just listen on HTTP
	log.Infof("Starting phishing server at http://%s", ps.config.ListenURL)
	return ps.server.ListenAndServe()
}

// Shutdown attempts to gracefully shutdown the server.
func (ps *PhishingServer) Shutdown() error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	return ps.server.Shutdown(ctx)
}

// CreatePhishingRouter creates the router that handles phishing connections.
func (ps *PhishingServer) registerRoutes() {
	router := mux.NewRouter()
	fileServer := http.FileServer(unindexed.Dir("./static/endpoint/"))
	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer))
	router.HandleFunc("/track", ps.TrackHandler)
	router.HandleFunc("/robots.txt", ps.RobotsHandler)
	router.HandleFunc("/{path:.*}/track", ps.TrackHandler)
	router.HandleFunc("/{path:.*}/report", ps.ReportHandler)
	router.HandleFunc("/report", ps.ReportHandler)
	router.HandleFunc("/{path:.*}", ps.PhishHandler)

	// Setup GZIP compression
	gzipWrapper, _ := gziphandler.NewGzipLevelHandler(gzip.BestCompression)
	phishHandler := gzipWrapper(router)

	// Setup logging
	phishHandler = handlers.CombinedLoggingHandler(log.Writer(), phishHandler)
	ps.server.Handler = phishHandler
}

// TrackHandler tracks emails as they are opened, updating the status for the given Result
func (ps *PhishingServer) TrackHandler(w http.ResponseWriter, r *http.Request) {
	r, err := setupContext(r)
	if err != nil {
		// Log the error if it wasn't something we can safely ignore
		if err != ErrInvalidRequest && err != ErrCampaignComplete {
			log.Error(err)
		}
		http.NotFound(w, r)
		return
	}
	// Check for a preview
	if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
		http.ServeFile(w, r, "static/images/pixel.png")
		return
	}
	rs := ctx.Get(r, "result").(models.Result)
	rid := ctx.Get(r, "rid").(string)
	d := ctx.Get(r, "details").(models.EventDetails)

	// Check for a transparency request
	if strings.HasSuffix(rid, TransparencySuffix) {
		ps.TransparencyHandler(w, r)
		return
	}

	err = rs.HandleEmailOpened(d)
	if err != nil {
		log.Error(err)
	}
	http.ServeFile(w, r, "static/images/pixel.png")
}

// ReportHandler tracks emails as they are reported, updating the status for the given Result
func (ps *PhishingServer) ReportHandler(w http.ResponseWriter, r *http.Request) {
	r, err := setupContext(r)
	if err != nil {
		// Log the error if it wasn't something we can safely ignore
		if err != ErrInvalidRequest && err != ErrCampaignComplete {
			log.Error(err)
		}
		http.NotFound(w, r)
		return
	}
	// Check for a preview
	if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
		w.WriteHeader(http.StatusNoContent)
		return
	}
	rs := ctx.Get(r, "result").(models.Result)
	rid := ctx.Get(r, "rid").(string)
	d := ctx.Get(r, "details").(models.EventDetails)

	// Check for a transparency request
	if strings.HasSuffix(rid, TransparencySuffix) {
		ps.TransparencyHandler(w, r)
		return
	}

	err = rs.HandleEmailReport(d)
	if err != nil {
		log.Error(err)
	}
	w.WriteHeader(http.StatusNoContent)
}

// PhishHandler handles incoming client connections and registers the associated actions performed
// (such as clicked link, etc.)
func (ps *PhishingServer) PhishHandler(w http.ResponseWriter, r *http.Request) {
	r, err := setupContext(r)
	if err != nil {
		// Log the error if it wasn't something we can safely ignore
		if err != ErrInvalidRequest && err != ErrCampaignComplete {
			log.Error(err)
		}
		http.NotFound(w, r)
		return
	}
	var ptx models.PhishingTemplateContext
	// Check for a preview
	if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
		ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
		if err != nil {
			log.Error(err)
			http.NotFound(w, r)
			return
		}
		p, err := models.GetPage(preview.PageId, preview.UserId)
		if err != nil {
			log.Error(err)
			http.NotFound(w, r)
			return
		}
		renderPhishResponse(w, r, ptx, p)
		return
	}
	rs := ctx.Get(r, "result").(models.Result)
	rid := ctx.Get(r, "rid").(string)
	c := ctx.Get(r, "campaign").(models.Campaign)
	d := ctx.Get(r, "details").(models.EventDetails)

	// Check for a transparency request
	if strings.HasSuffix(rid, TransparencySuffix) {
		ps.TransparencyHandler(w, r)
		return
	}

	p, err := models.GetPage(c.PageId, c.UserId)
	if err != nil {
		log.Error(err)
		http.NotFound(w, r)
		return
	}
	switch {
	case r.Method == "GET":
		err = rs.HandleClickedLink(d)
		if err != nil {
			log.Error(err)
		}
	case r.Method == "POST":
		err = rs.HandleFormSubmit(d)
		if err != nil {
			log.Error(err)
		}
	}
	ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
	if err != nil {
		log.Error(err)
		http.NotFound(w, r)
	}
	renderPhishResponse(w, r, ptx, p)
}

// renderPhishResponse handles rendering the correct response to the phishing
// connection. This usually involves writing out the page HTML or redirecting
// the user to the correct URL.
func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) {
	// If the request was a form submit and a redirect URL was specified, we
	// should send the user to that URL
	if r.Method == "POST" {
		if p.RedirectURL != "" {
			redirectURL, err := models.ExecuteTemplate(p.RedirectURL, ptx)
			if err != nil {
				log.Error(err)
				http.NotFound(w, r)
				return
			}
			http.Redirect(w, r, redirectURL, http.StatusFound)
			return
		}
	}
	// Otherwise, we just need to write out the templated HTML
	html, err := models.ExecuteTemplate(p.HTML, ptx)
	if err != nil {
		log.Error(err)
		http.NotFound(w, r)
		return
	}
	w.Write([]byte(html))
}

// RobotsHandler prevents search engines, etc. from indexing phishing materials
func (ps *PhishingServer) RobotsHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "User-agent: *\nDisallow: /")
}

// TransparencyHandler returns a TransparencyResponse for the provided result
// and campaign.
func (ps *PhishingServer) TransparencyHandler(w http.ResponseWriter, r *http.Request) {
	rs := ctx.Get(r, "result").(models.Result)
	tr := &TransparencyResponse{
		Server:         config.ServerName,
		SendDate:       rs.SendDate,
		ContactAddress: ps.contactAddress,
	}
	api.JSONResponse(w, tr, http.StatusOK)
}

// setupContext handles some of the administrative work around receiving a new
// request, such as checking the result ID, the campaign, etc.
func setupContext(r *http.Request) (*http.Request, error) {
	err := r.ParseForm()
	if err != nil {
		log.Error(err)
		return r, err
	}
	rid := r.Form.Get(models.RecipientParameter)
	if rid == "" {
		return r, ErrInvalidRequest
	}
	// Since we want to support the common case of adding a "+" to indicate a
	// transparency request, we need to take care to handle the case where the
	// request ends with a space, since a "+" is technically reserved for use
	// as a URL encoding of a space.
	if strings.HasSuffix(rid, " ") {
		// We'll trim off the space
		rid = strings.TrimRight(rid, " ")
		// Then we'll add the transparency suffix
		rid = fmt.Sprintf("%s%s", rid, TransparencySuffix)
	}
	// Finally, if this is a transparency request, we'll need to verify that
	// a valid rid has been provided, so we'll look up the result with a
	// trimmed parameter.
	id := strings.TrimSuffix(rid, TransparencySuffix)
	// Check to see if this is a preview or a real result
	if strings.HasPrefix(id, models.PreviewPrefix) {
		rs, err := models.GetEmailRequestByResultId(id)
		if err != nil {
			return r, err
		}
		r = ctx.Set(r, "result", rs)
		return r, nil
	}
	rs, err := models.GetResult(id)
	if err != nil {
		return r, err
	}
	c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
	if err != nil {
		log.Error(err)
		return r, err
	}
	// Don't process events for completed campaigns
	if c.Status == models.CampaignComplete {
		return r, ErrCampaignComplete
	}
	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		log.Error(err)
		return r, err
	}
	// Respect X-Forwarded headers
	if fips := r.Header.Get("X-Forwarded-For"); fips != "" {
		ip = strings.Split(fips, ", ")[0]
	}
	// Handle post processing such as GeoIP
	err = rs.UpdateGeo(ip)
	if err != nil {
		log.Error(err)
	}
	d := models.EventDetails{
		Payload: r.Form,
		Browser: make(map[string]string),
	}
	d.Browser["address"] = ip
	d.Browser["user-agent"] = r.Header.Get("User-Agent")

	r = ctx.Set(r, "rid", rid)
	r = ctx.Set(r, "result", rs)
	r = ctx.Set(r, "campaign", c)
	r = ctx.Set(r, "details", d)
	return r, nil
}
<commit_msg>Add CORS support for Reporting Handler (#1529)<commit_after>package controllers

import (
	"compress/gzip"
	"context"
	"errors"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/NYTimes/gziphandler"
	"github.com/gophish/gophish/config"
	ctx "github.com/gophish/gophish/context"
	"github.com/gophish/gophish/controllers/api"
	log "github.com/gophish/gophish/logger"
	"github.com/gophish/gophish/models"
	"github.com/gophish/gophish/util"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/jordan-wright/unindexed"
)

// ErrInvalidRequest is thrown when a request with an invalid structure is
// received
var ErrInvalidRequest = errors.New("Invalid request")

// ErrCampaignComplete is thrown when an event is received for a campaign that
// has already been marked as complete.
var ErrCampaignComplete = errors.New("Event received on completed campaign")

// TransparencyResponse is the JSON response provided when a third-party
// makes a request to the transparency handler.
type TransparencyResponse struct {
	Server         string    `json:"server"`
	ContactAddress string    `json:"contact_address"`
	SendDate       time.Time `json:"send_date"`
}

// TransparencySuffix (when appended to a valid result ID), will cause Gophish
// to return a transparency response.
const TransparencySuffix = "+"

// PhishingServerOption is a functional option that is used to configure the
// the phishing server
type PhishingServerOption func(*PhishingServer)

// PhishingServer is an HTTP server that implements the campaign event
// handlers, such as email open tracking, click tracking, and more.
type PhishingServer struct {
	server         *http.Server
	config         config.PhishServer
	contactAddress string
}

// NewPhishingServer returns a new instance of the phishing server with
// provided options applied.
func NewPhishingServer(config config.PhishServer, options ...PhishingServerOption) *PhishingServer {
	defaultServer := &http.Server{
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		Addr:         config.ListenURL,
	}
	ps := &PhishingServer{
		server: defaultServer,
		config: config,
	}
	for _, opt := range options {
		opt(ps)
	}
	ps.registerRoutes()
	return ps
}

// WithContactAddress sets the contact address used by the transparency
// handlers
func WithContactAddress(addr string) PhishingServerOption {
	return func(ps *PhishingServer) {
		ps.contactAddress = addr
	}
}

// Start launches the phishing server, listening on the configured address.
func (ps *PhishingServer) Start() error {
	if ps.config.UseTLS {
		err := util.CheckAndCreateSSL(ps.config.CertPath, ps.config.KeyPath)
		if err != nil {
			log.Fatal(err)
			return err
		}
		log.Infof("Starting phishing server at https://%s", ps.config.ListenURL)
		return ps.server.ListenAndServeTLS(ps.config.CertPath, ps.config.KeyPath)
	}
	// If TLS isn't configured, just listen on HTTP
	log.Infof("Starting phishing server at http://%s", ps.config.ListenURL)
	return ps.server.ListenAndServe()
}

// Shutdown attempts to gracefully shutdown the server.
func (ps *PhishingServer) Shutdown() error {
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
	defer cancel()
	return ps.server.Shutdown(ctx)
}

// CreatePhishingRouter creates the router that handles phishing connections.
func (ps *PhishingServer) registerRoutes() {
	router := mux.NewRouter()
	fileServer := http.FileServer(unindexed.Dir("./static/endpoint/"))
	router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", fileServer))
	router.HandleFunc("/track", ps.TrackHandler)
	router.HandleFunc("/robots.txt", ps.RobotsHandler)
	router.HandleFunc("/{path:.*}/track", ps.TrackHandler)
	router.HandleFunc("/{path:.*}/report", ps.ReportHandler)
	router.HandleFunc("/report", ps.ReportHandler)
	router.HandleFunc("/{path:.*}", ps.PhishHandler)

	// Setup GZIP compression
	gzipWrapper, _ := gziphandler.NewGzipLevelHandler(gzip.BestCompression)
	phishHandler := gzipWrapper(router)

	// Setup logging
	phishHandler = handlers.CombinedLoggingHandler(log.Writer(), phishHandler)
	ps.server.Handler = phishHandler
}

// TrackHandler tracks emails as they are opened, updating the status for the given Result
func (ps *PhishingServer) TrackHandler(w http.ResponseWriter, r *http.Request) {
	r, err := setupContext(r)
	if err != nil {
		// Log the error if it wasn't something we can safely ignore
		if err != ErrInvalidRequest && err != ErrCampaignComplete {
			log.Error(err)
		}
		http.NotFound(w, r)
		return
	}
	// Check for a preview
	if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
		http.ServeFile(w, r, "static/images/pixel.png")
		return
	}
	rs := ctx.Get(r, "result").(models.Result)
	rid := ctx.Get(r, "rid").(string)
	d := ctx.Get(r, "details").(models.EventDetails)

	// Check for a transparency request
	if strings.HasSuffix(rid, TransparencySuffix) {
		ps.TransparencyHandler(w, r)
		return
	}

	err = rs.HandleEmailOpened(d)
	if err != nil {
		log.Error(err)
	}
	http.ServeFile(w, r, "static/images/pixel.png")
}

// ReportHandler tracks emails as they are reported, updating the status for the given Result
func (ps *PhishingServer) ReportHandler(w http.ResponseWriter, r *http.Request) {
	r, err := setupContext(r)
	w.Header().Set("Access-Control-Allow-Origin", "*") // To allow Chrome extensions (or other pages) to report a campaign without violating CORS
	if err != nil {
		// Log the error if it wasn't something we can safely ignore
		if err != ErrInvalidRequest && err != ErrCampaignComplete {
			log.Error(err)
		}
		http.NotFound(w, r)
		return
	}
	// Check for a preview
	if _, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
		w.WriteHeader(http.StatusNoContent)
		return
	}
	rs := ctx.Get(r, "result").(models.Result)
	rid := ctx.Get(r, "rid").(string)
	d := ctx.Get(r, "details").(models.EventDetails)

	// Check for a transparency request
	if strings.HasSuffix(rid, TransparencySuffix) {
		ps.TransparencyHandler(w, r)
		return
	}

	err = rs.HandleEmailReport(d)
	if err != nil {
		log.Error(err)
	}
	w.WriteHeader(http.StatusNoContent)
}

// PhishHandler handles incoming client connections and registers the associated actions performed
// (such as clicked link, etc.)
func (ps *PhishingServer) PhishHandler(w http.ResponseWriter, r *http.Request) {
	r, err := setupContext(r)
	if err != nil {
		// Log the error if it wasn't something we can safely ignore
		if err != ErrInvalidRequest && err != ErrCampaignComplete {
			log.Error(err)
		}
		http.NotFound(w, r)
		return
	}
	w.Header().Set("X-Server", config.ServerName) // Useful for checking if this is a GoPhish server (e.g. for campaign reporting plugins)
	var ptx models.PhishingTemplateContext
	// Check for a preview
	if preview, ok := ctx.Get(r, "result").(models.EmailRequest); ok {
		ptx, err = models.NewPhishingTemplateContext(&preview, preview.BaseRecipient, preview.RId)
		if err != nil {
			log.Error(err)
			http.NotFound(w, r)
			return
		}
		p, err := models.GetPage(preview.PageId, preview.UserId)
		if err != nil {
			log.Error(err)
			http.NotFound(w, r)
			return
		}
		renderPhishResponse(w, r, ptx, p)
		return
	}
	rs := ctx.Get(r, "result").(models.Result)
	rid := ctx.Get(r, "rid").(string)
	c := ctx.Get(r, "campaign").(models.Campaign)
	d := ctx.Get(r, "details").(models.EventDetails)

	// Check for a transparency request
	if strings.HasSuffix(rid, TransparencySuffix) {
		ps.TransparencyHandler(w, r)
		return
	}

	p, err := models.GetPage(c.PageId, c.UserId)
	if err != nil {
		log.Error(err)
		http.NotFound(w, r)
		return
	}
	switch {
	case r.Method == "GET":
		err = rs.HandleClickedLink(d)
		if err != nil {
			log.Error(err)
		}
	case r.Method == "POST":
		err = rs.HandleFormSubmit(d)
		if err != nil {
			log.Error(err)
		}
	}
	ptx, err = models.NewPhishingTemplateContext(&c, rs.BaseRecipient, rs.RId)
	if err != nil {
		log.Error(err)
		http.NotFound(w, r)
	}
	renderPhishResponse(w, r, ptx, p)
}

// renderPhishResponse handles rendering the correct response to the phishing
// connection. This usually involves writing out the page HTML or redirecting
// the user to the correct URL.
func renderPhishResponse(w http.ResponseWriter, r *http.Request, ptx models.PhishingTemplateContext, p models.Page) {
	// If the request was a form submit and a redirect URL was specified, we
	// should send the user to that URL
	if r.Method == "POST" {
		if p.RedirectURL != "" {
			redirectURL, err := models.ExecuteTemplate(p.RedirectURL, ptx)
			if err != nil {
				log.Error(err)
				http.NotFound(w, r)
				return
			}
			http.Redirect(w, r, redirectURL, http.StatusFound)
			return
		}
	}
	// Otherwise, we just need to write out the templated HTML
	html, err := models.ExecuteTemplate(p.HTML, ptx)
	if err != nil {
		log.Error(err)
		http.NotFound(w, r)
		return
	}
	w.Write([]byte(html))
}

// RobotsHandler prevents search engines, etc. from indexing phishing materials
func (ps *PhishingServer) RobotsHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "User-agent: *\nDisallow: /")
}

// TransparencyHandler returns a TransparencyResponse for the provided result
// and campaign.
func (ps *PhishingServer) TransparencyHandler(w http.ResponseWriter, r *http.Request) {
	rs := ctx.Get(r, "result").(models.Result)
	tr := &TransparencyResponse{
		Server:         config.ServerName,
		SendDate:       rs.SendDate,
		ContactAddress: ps.contactAddress,
	}
	api.JSONResponse(w, tr, http.StatusOK)
}

// setupContext handles some of the administrative work around receiving a new
// request, such as checking the result ID, the campaign, etc.
func setupContext(r *http.Request) (*http.Request, error) {
	err := r.ParseForm()
	if err != nil {
		log.Error(err)
		return r, err
	}
	rid := r.Form.Get(models.RecipientParameter)
	if rid == "" {
		return r, ErrInvalidRequest
	}
	// Since we want to support the common case of adding a "+" to indicate a
	// transparency request, we need to take care to handle the case where the
	// request ends with a space, since a "+" is technically reserved for use
	// as a URL encoding of a space.
	if strings.HasSuffix(rid, " ") {
		// We'll trim off the space
		rid = strings.TrimRight(rid, " ")
		// Then we'll add the transparency suffix
		rid = fmt.Sprintf("%s%s", rid, TransparencySuffix)
	}
	// Finally, if this is a transparency request, we'll need to verify that
	// a valid rid has been provided, so we'll look up the result with a
	// trimmed parameter.
	id := strings.TrimSuffix(rid, TransparencySuffix)
	// Check to see if this is a preview or a real result
	if strings.HasPrefix(id, models.PreviewPrefix) {
		rs, err := models.GetEmailRequestByResultId(id)
		if err != nil {
			return r, err
		}
		r = ctx.Set(r, "result", rs)
		return r, nil
	}
	rs, err := models.GetResult(id)
	if err != nil {
		return r, err
	}
	c, err := models.GetCampaign(rs.CampaignId, rs.UserId)
	if err != nil {
		log.Error(err)
		return r, err
	}
	// Don't process events for completed campaigns
	if c.Status == models.CampaignComplete {
		return r, ErrCampaignComplete
	}
	ip, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		log.Error(err)
		return r, err
	}
	// Respect X-Forwarded headers
	if fips := r.Header.Get("X-Forwarded-For"); fips != "" {
		ip = strings.Split(fips, ", ")[0]
	}
	// Handle post processing such as GeoIP
	err = rs.UpdateGeo(ip)
	if err != nil {
		log.Error(err)
	}
	d := models.EventDetails{
		Payload: r.Form,
		Browser: make(map[string]string),
	}
	d.Browser["address"] = ip
	d.Browser["user-agent"] = r.Header.Get("User-Agent")

	r = ctx.Set(r, "rid", rid)
	r = ctx.Set(r, "result", rs)
	r = ctx.Set(r, "campaign", c)
	r = ctx.Set(r, "details", d)
	return r, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
)

type dockerPullStat struct {
	ExitCode int
	Duration time.Duration
}

type dockerRunStat struct {
	ExitCode int
	Duration time.Duration
}

type estafettePipelineStat struct {
	Pipeline       estafettePipeline
	DockerPullStat dockerPullStat
	DockerRunStat  dockerRunStat
}

func (c *estafettePipelineStat) ExitCode() int {
	if c.DockerPullStat.ExitCode > 0 {
		return c.DockerPullStat.ExitCode
	}
	if c.DockerRunStat.ExitCode > 0 {
		return c.DockerRunStat.ExitCode
	}
	return 0
}

func runDockerPull(p estafettePipeline) (stat dockerPullStat, err error) {

	start := time.Now()

	fmt.Printf("[estafette] Pulling docker container '%v'\n", p.ContainerImage)

	cli, err := client.NewEnvClient()
	if err != nil {
		return stat, err
	}

	rc, err := cli.ImagePull(context.Background(), p.ContainerImage, types.ImagePullOptions{})
	defer rc.Close()
	if err != nil {
		return stat, err
	}

	// wait for image pull to finish
	ioutil.ReadAll(rc)

	stat.Duration = time.Since(start)

	return
}

func runDockerRun(dir string, envvars map[string]string, p estafettePipeline) (stat dockerRunStat, err error) {

	// run docker with image and commands from yaml
	start := time.Now()

	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		return stat, err
	}

	// define commands
	cmdSlice := make([]string, 0)
	cmdSlice = append(cmdSlice, p.Shell)
	cmdSlice = append(cmdSlice, "-c")
	cmdSlice = append(cmdSlice, "set -e;"+os.ExpandEnv(strings.Join(p.Commands, ";")))

	// define envvars
	envVars := make([]string, 0)
	if envvars != nil && len(envvars) > 0 {
		for k, v := range envvars {
			envVars = append(envVars, fmt.Sprintf("\"%v=%v\"", k, v))
		}
	}

	// define entrypoint
	entrypoint := make([]string, 0)
	entrypoint = append(entrypoint, "")

	// define binds
	binds := make([]string, 0)
	binds = append(binds, fmt.Sprintf("%v:%v", dir, os.ExpandEnv(p.WorkingDirectory)))
	binds = append(binds, "/var/run/docker.sock:/var/run/docker.sock")
	binds = append(binds, "/var/run/secrets/kubernetes.io/serviceaccount:/var/run/secrets/kubernetes.io/serviceaccount")

	// create container
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		AttachStdout: true,
		AttachStderr: true,
		Env:          envVars,
		Cmd:          cmdSlice,
		Image:        p.ContainerImage,
		WorkingDir:   os.ExpandEnv(p.WorkingDirectory),
		Entrypoint:   entrypoint,
	}, &container.HostConfig{
		Binds:      binds,
		AutoRemove: true,
		Privileged: true,
	}, &network.NetworkingConfig{}, "")
	if err != nil {
		return stat, err
	}

	// start container
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return stat, err
	}

	// follow logs
	rc, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Follow:     true,
	})
	defer rc.Close()
	if err != nil {
		return stat, err
	}

	// stream logs to stdout with buffering
	in := bufio.NewScanner(rc)
	for in.Scan() {
		fmt.Printf("[estafette] [%v] %s\n", p.Name, in.Text()) // write each line to your log, or anything you need
	}
	if err := in.Err(); err != nil {
		fmt.Printf("[estafette] [%v] Error: %s\n", p.Name, err)
	}

	// wait for container to stop run
	if _, err = cli.ContainerWait(ctx, resp.ID); err != nil {
		return stat, err
	}

	stat.Duration = time.Since(start)

	return
}

// https://gist.github.com/elwinar/14e1e897fdbe4d3432e1
func toUpperSnake(in string) string {
	runes := []rune(in)
	length := len(runes)

	var out []rune
	for i := 0; i < length; i++ {
		if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
			out = append(out, '_')
		}
		out = append(out, unicode.ToUpper(runes[i]))
	}

	return string(out)
}

func collectEstafetteEnvvars(m estafetteManifest) (envvars map[string]string) {

	envvars = map[string]string{}

	for _, e := range os.Environ() {
		kvPair := strings.Split(e, "=")
		if len(kvPair) == 2 {
			envvarName := kvPair[0]
			envvarValue := kvPair[1]

			if strings.HasPrefix(envvarName, "ESTAFETTE_") {
				envvars[envvarName] = envvarValue
			}
		}
	}

	// add the labels as envvars
	if m.Labels != nil && len(m.Labels) > 0 {
		for key, value := range m.Labels {

			envvarName := "ESTAFETTE_LABEL_" + toUpperSnake(key)
			envvars[envvarName] = value

			os.Setenv(envvarName, value)
		}
	}

	return
}

func runPipeline(dir string, envvars map[string]string, p estafettePipeline) (stat estafettePipelineStat, err error) {

	stat.Pipeline = p

	fmt.Printf("[estafette] Starting pipeline '%v'\n", p.Name)

	// pull docker image
	stat.DockerPullStat, err = runDockerPull(p)
	if err != nil {
		return
	}

	stat.DockerRunStat, err = runDockerRun(dir, envvars, p)
	if err != nil {
		return
	}

	fmt.Printf("[estafette] Finished pipeline '%v' successfully\n", p.Name)

	return
}
<commit_msg>print docker logs with %v<commit_after>package main

import (
	"bufio"
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"
	"unicode"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"github.com/docker/docker/client"
)

type dockerPullStat struct {
	ExitCode int
	Duration time.Duration
}

type dockerRunStat struct {
	ExitCode int
	Duration time.Duration
}

type estafettePipelineStat struct {
	Pipeline       estafettePipeline
	DockerPullStat dockerPullStat
	DockerRunStat  dockerRunStat
}

func (c *estafettePipelineStat) ExitCode() int {
	if c.DockerPullStat.ExitCode > 0 {
		return c.DockerPullStat.ExitCode
	}
	if c.DockerRunStat.ExitCode > 0 {
		return c.DockerRunStat.ExitCode
	}
	return 0
}

func runDockerPull(p estafettePipeline) (stat dockerPullStat, err error) {

	start := time.Now()

	fmt.Printf("[estafette] Pulling docker container '%v'\n", p.ContainerImage)

	cli, err := client.NewEnvClient()
	if err != nil {
		return stat, err
	}

	rc, err := cli.ImagePull(context.Background(), p.ContainerImage, types.ImagePullOptions{})
	defer rc.Close()
	if err != nil {
		return stat, err
	}

	// wait for image pull to finish
	ioutil.ReadAll(rc)

	stat.Duration = time.Since(start)

	return
}

func runDockerRun(dir string, envvars map[string]string, p estafettePipeline) (stat dockerRunStat, err error) {

	// run docker with image and commands from yaml
	start := time.Now()

	ctx := context.Background()
	cli, err := client.NewEnvClient()
	if err != nil {
		return stat, err
	}

	// define commands
	cmdSlice := make([]string, 0)
	cmdSlice = append(cmdSlice, p.Shell)
	cmdSlice = append(cmdSlice, "-c")
	cmdSlice = append(cmdSlice, "set -e;"+os.ExpandEnv(strings.Join(p.Commands, ";")))

	// define envvars
	envVars := make([]string, 0)
	if envvars != nil && len(envvars) > 0 {
		for k, v := range envvars {
			envVars = append(envVars, fmt.Sprintf("\"%v=%v\"", k, v))
		}
	}

	// define entrypoint
	entrypoint := make([]string, 0)
	entrypoint = append(entrypoint, "")

	// define binds
	binds := make([]string, 0)
	binds = append(binds, fmt.Sprintf("%v:%v", dir, os.ExpandEnv(p.WorkingDirectory)))
	binds = append(binds, "/var/run/docker.sock:/var/run/docker.sock")
	binds = append(binds, "/var/run/secrets/kubernetes.io/serviceaccount:/var/run/secrets/kubernetes.io/serviceaccount")

	// create container
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		AttachStdout: true,
		AttachStderr: true,
		Env:          envVars,
		Cmd:          cmdSlice,
		Image:        p.ContainerImage,
		WorkingDir:   os.ExpandEnv(p.WorkingDirectory),
		Entrypoint:   entrypoint,
	}, &container.HostConfig{
		Binds:      binds,
		AutoRemove: true,
		Privileged: true,
	}, &network.NetworkingConfig{}, "")
	if err != nil {
		return stat, err
	}

	// start container
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
		return stat, err
	}

	// follow logs
	rc, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{
		ShowStdout: true,
		ShowStderr: true,
		Follow:     true,
	})
	defer rc.Close()
	if err != nil {
		return stat, err
	}

	// stream logs to stdout with buffering
	in := bufio.NewScanner(rc)
	for in.Scan() {
		fmt.Printf("[estafette] [%v] %v\n", p.Name, in.Text()) // write each line to your log, or anything you need
	}
	if err := in.Err(); err != nil {
		fmt.Printf("[estafette] [%v] Error: %v\n", p.Name, err)
	}

	// wait for container to stop run
	if _, err = cli.ContainerWait(ctx, resp.ID); err != nil {
		return stat, err
	}

	stat.Duration = time.Since(start)

	return
}

// https://gist.github.com/elwinar/14e1e897fdbe4d3432e1
func toUpperSnake(in string) string {
	runes := []rune(in)
	length := len(runes)

	var out []rune
	for i := 0; i < length; i++ {
		if i > 0 && unicode.IsUpper(runes[i]) && ((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {
			out = append(out, '_')
		}
		out = append(out, unicode.ToUpper(runes[i]))
	}

	return string(out)
}

func collectEstafetteEnvvars(m estafetteManifest) (envvars map[string]string) {

	envvars = map[string]string{}

	for _, e := range os.Environ() {
		kvPair := strings.Split(e, "=")
		if len(kvPair) == 2 {
			envvarName := kvPair[0]
			envvarValue := kvPair[1]

			if strings.HasPrefix(envvarName, "ESTAFETTE_") {
				envvars[envvarName] = envvarValue
			}
		}
	}

	// add the labels as envvars
	if m.Labels != nil && len(m.Labels) > 0 {
		for key, value := range m.Labels {

			envvarName := "ESTAFETTE_LABEL_" + toUpperSnake(key)
			envvars[envvarName] = value

			os.Setenv(envvarName, value)
		}
	}

	return
}

func runPipeline(dir string, envvars map[string]string, p estafettePipeline) (stat estafettePipelineStat, err error) {

	stat.Pipeline = p

	fmt.Printf("[estafette] Starting pipeline '%v'\n", p.Name)

	// pull docker image
	stat.DockerPullStat, err = runDockerPull(p)
	if err != nil {
		return
	}

	stat.DockerRunStat, err = runDockerRun(dir, envvars, p)
	if err != nil {
		return
	}

	fmt.Printf("[estafette] Finished pipeline '%v' successfully\n", p.Name)

	return
}
<|endoftext|>