<commit_before>package main

import "fmt"

func main() {
	// array is one of three collection type built into the Go language.
	// The type [n]T is an array of n values of type T.
	// array are numbered (indexed) sequence of elemnts of a single specifed type.
	// some intresting properties of go array's are:
	// an array identifies a contigous memory segment
	// the length of the array is part of the type
	// once defined the size of the array cannot be changed
	// array index are zero based like many other languages

	// declare an array
	var numbers [3]int64
	numbers[0] = 1
	numbers[1] = 23
	// Trying to access or set an index that does not exists, results in compile-time error,
	// since go compiler does array bound ckecking
	// uncomment the following line to see the error in action
	//numbers[3] = 98

	// array elements are zeroed .i.e. the elements that are not assigned a value,
	// will intialized to their respective types zero value. For example in
	// case of numbers array the value of third (3) element would set to zero (0)
	fmt.Printf("numbers = %#v\n", numbers)

	// declare an array using the literal syntax
	carModels := [3]string{"Fusion", "Fiesta", "Mustang"}
	fmt.Printf("carModels = %#v\n", carModels)

	// you can use an ellipsis to specify an implicit length when you pass the values
	// compiler would count the number elements and fill in the ellipsis
	weekDays := [...]string{"Mon", "Tue", "Wed", "Thus", "Fri", "Sat", "Sun"}
	fmt.Printf("Days in week %#v\n", weekDays)

	// you can determine the length using the len function
	fmt.Printf("Number of elements in carModels %d\n", len(carModels))

	// use the subscript notation to access an array element
	fmt.Printf("First day of the week is %s\n", weekDays[0])

	// you can also iterate over the array using for..range loop
	for indx, val := range carModels {
		fmt.Printf("car at index %d is %s\n", indx, val)
	}

	// or using for loop
	for m := 0; m < len(carModels); m++ {
		fmt.Printf("For: element at index %d is %s\n", m, carModels[m])
	}

	// multi-dimensional arrays
	var arr [2][3]string
	for i := 0; i < 2; i++ {
		for j := 0; j < 3; j++ {
			arr[i][j] = fmt.Sprintf("r%d-c%d", i+1, j+1)
		}
	}
	fmt.Printf("%q\n", arr)
}
<commit_msg>size of multi-dimensional arrays<commit_after>package main

import "fmt"

func main() {
	// array is one of three collection type built into the Go language.
	// The type [n]T is an array of n values of type T.
	// array are numbered (indexed) sequence of elemnts of a single specifed type.
	// some intresting properties of go array's are:
	// an array identifies a contigous memory segment
	// the length of the array is part of the type
	// once defined the size of the array cannot be changed
	// array index are zero based like many other languages

	// declare an array
	var numbers [3]int64
	numbers[0] = 1
	numbers[1] = 23
	// Trying to access or set an index that does not exists, results in compile-time error,
	// since go compiler does array bound ckecking
	// uncomment the following line to see the error in action
	//numbers[3] = 98

	// array elements are zeroed .i.e. the elements that are not assigned a value,
	// will intialized to their respective types zero value. For example in
	// case of numbers array the value of third (3) element would set to zero (0)
	fmt.Printf("numbers = %#v\n", numbers)

	// declare an array using the literal syntax
	carModels := [3]string{"Fusion", "Fiesta", "Mustang"}
	fmt.Printf("carModels = %#v\n", carModels)

	// you can use an ellipsis to specify an implicit length when you pass the values
	// compiler would count the number elements and fill in the ellipsis
	weekDays := [...]string{"Mon", "Tue", "Wed", "Thus", "Fri", "Sat", "Sun"}
	fmt.Printf("Days in week %#v\n", weekDays)

	// you can determine the length using the len function
	fmt.Printf("Number of elements in carModels %d\n", len(carModels))

	// use the subscript notation to access an array element
	fmt.Printf("First day of the week is %s\n", weekDays[0])

	// you can also iterate over the array using for..range loop
	for indx, val := range carModels {
		fmt.Printf("car at index %d is %s\n", indx, val)
	}

	// or using for loop
	for m := 0; m < len(carModels); m++ {
		fmt.Printf("For: element at index %d is %s\n", m, carModels[m])
	}

	// multi-dimensional arrays
	var arr [2][3]string
	for i := 0; i < 2; i++ {
		for j := 0; j < 3; j++ {
			arr[i][j] = fmt.Sprintf("r%d-c%d", i+1, j+1)
		}
	}
	fmt.Printf("%q\n", arr)

    // size of multi-dimensional arrays
    var a [3][5]int8
    for _, h := range a {
        fmt.Println(len(h)) // each one prints 5
    }
    fmt.Println(len(a))  // prints 3, the length of the external array

}
<|endoftext|>


<commit_before>// +build windows

package cwriter

import (
	"syscall"
	"unsafe"

	"github.com/mattn/go-isatty"
)

var kernel32 = syscall.NewLazyDLL("kernel32.dll")

var (
	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
	procSetConsoleCursorPosition   = kernel32.NewProc("SetConsoleCursorPosition")
	procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
)

type coord struct {
	x int16
	y int16
}

type smallRect struct {
	left   int16
	top    int16
	right  int16
	bottom int16
}

type consoleScreenBufferInfo struct {
	size              coord
	cursorPosition    coord
	attributes        uint16
	window            smallRect
	maximumWindowSize coord
}

func (w *Writer) clearLines() {
	if !w.isTerminal && isatty.IsCygwinTerminal(w.fd) {
		w.ansiCuuAndEd()
	}

	info := new(consoleScreenBufferInfo)
	procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(info)))

	info.cursorPosition.y -= int16(w.lineCount)
	if info.cursorPosition.y < 0 {
		info.cursorPosition.y = 0
	}
	procSetConsoleCursorPosition.Call(w.fd, uintptr(uint32(uint16(info.cursorPosition.y))<<16|uint32(uint16(info.cursorPosition.x))))

	// clear the lines
	cursor := &coord{
		x: info.window.left,
		y: info.cursorPosition.y,
	}
	count := uint32(info.size.x) * uint32(w.lineCount)
	procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(cursor)), uintptr(unsafe.Pointer(new(uint32))))
}

// GetSize returns the visible dimensions of the given terminal.
//
// These dimensions don't include any scrollback buffer height.
func GetSize(fd uintptr) (width, height int, err error) {
	info := new(consoleScreenBufferInfo)
	procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(info)))
	return int(info.window.right - info.window.left), int(info.window.bottom - info.window.top), nil
}
<commit_msg>missing return<commit_after>// +build windows

package cwriter

import (
	"syscall"
	"unsafe"

	"github.com/mattn/go-isatty"
)

var kernel32 = syscall.NewLazyDLL("kernel32.dll")

var (
	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
	procSetConsoleCursorPosition   = kernel32.NewProc("SetConsoleCursorPosition")
	procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
)

type coord struct {
	x int16
	y int16
}

type smallRect struct {
	left   int16
	top    int16
	right  int16
	bottom int16
}

type consoleScreenBufferInfo struct {
	size              coord
	cursorPosition    coord
	attributes        uint16
	window            smallRect
	maximumWindowSize coord
}

func (w *Writer) clearLines() {
	if !w.isTerminal && isatty.IsCygwinTerminal(w.fd) {
		w.ansiCuuAndEd()
		return
	}

	info := new(consoleScreenBufferInfo)
	procGetConsoleScreenBufferInfo.Call(w.fd, uintptr(unsafe.Pointer(info)))

	info.cursorPosition.y -= int16(w.lineCount)
	if info.cursorPosition.y < 0 {
		info.cursorPosition.y = 0
	}
	procSetConsoleCursorPosition.Call(w.fd, uintptr(uint32(uint16(info.cursorPosition.y))<<16|uint32(uint16(info.cursorPosition.x))))

	// clear the lines
	cursor := &coord{
		x: info.window.left,
		y: info.cursorPosition.y,
	}
	count := uint32(info.size.x) * uint32(w.lineCount)
	procFillConsoleOutputCharacter.Call(w.fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(cursor)), uintptr(unsafe.Pointer(new(uint32))))
}

// GetSize returns the visible dimensions of the given terminal.
//
// These dimensions don't include any scrollback buffer height.
func GetSize(fd uintptr) (width, height int, err error) {
	info := new(consoleScreenBufferInfo)
	procGetConsoleScreenBufferInfo.Call(fd, uintptr(unsafe.Pointer(info)))
	return int(info.window.right - info.window.left), int(info.window.bottom - info.window.top), nil
}
<|endoftext|>


<commit_before>package daemon

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/execdriver"
	"github.com/docker/docker/pkg/pubsub"
	"github.com/docker/libcontainer/system"
)

// newStatsCollector returns a new statsCollector that collections
// network and cgroup stats for a registered container at the specified
// interval.  The collector allows non-running containers to be added
// and will start processing stats when they are started.
func newStatsCollector(interval time.Duration) *statsCollector {
	s := &statsCollector{
		interval:   interval,
		publishers: make(map[*Container]*pubsub.Publisher),
		clockTicks: uint64(system.GetClockTicks()),
	}
	go s.run()
	return s
}

// statsCollector manages and provides container resource stats
type statsCollector struct {
	m          sync.Mutex
	interval   time.Duration
	clockTicks uint64
	publishers map[*Container]*pubsub.Publisher
}

// collect registers the container with the collector and adds it to
// the event loop for collection on the specified interval returning
// a channel for the subscriber to receive on.
func (s *statsCollector) collect(c *Container) chan interface{} {
	s.m.Lock()
	defer s.m.Unlock()
	publisher, exists := s.publishers[c]
	if !exists {
		publisher = pubsub.NewPublisher(100*time.Millisecond, 1024)
		s.publishers[c] = publisher
	}
	return publisher.Subscribe()
}

// stopCollection closes the channels for all subscribers and removes
// the container from metrics collection.
func (s *statsCollector) stopCollection(c *Container) {
	s.m.Lock()
	if publisher, exists := s.publishers[c]; exists {
		publisher.Close()
		delete(s.publishers, c)
	}
	s.m.Unlock()
}

// unsubscribe removes a specific subscriber from receiving updates for a container's stats.
func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) {
	s.m.Lock()
	publisher := s.publishers[c]
	if publisher != nil {
		publisher.Evict(ch)
		if publisher.Len() == 0 {
			delete(s.publishers, c)
		}
	}
	s.m.Unlock()
}

func (s *statsCollector) run() {
	for range time.Tick(s.interval) {
		for container, publisher := range s.publishers {
			systemUsage, err := s.getSystemCpuUsage()
			if err != nil {
				logrus.Errorf("collecting system cpu usage for %s: %v", container.ID, err)
				continue
			}
			stats, err := container.Stats()
			if err != nil {
				if err != execdriver.ErrNotRunning {
					logrus.Errorf("collecting stats for %s: %v", container.ID, err)
				}
				continue
			}
			stats.SystemUsage = systemUsage
			publisher.Publish(stats)
		}
	}
}

const nanoSeconds = 1e9

// getSystemCpuUSage returns the host system's cpu usage in nanoseconds
// for the system to match the cgroup readings are returned in the same format.
func (s *statsCollector) getSystemCpuUsage() (uint64, error) {
	f, err := os.Open("/proc/stat")
	if err != nil {
		return 0, err
	}
	defer f.Close()
	sc := bufio.NewScanner(f)
	for sc.Scan() {
		parts := strings.Fields(sc.Text())
		switch parts[0] {
		case "cpu":
			if len(parts) < 8 {
				return 0, fmt.Errorf("invalid number of cpu fields")
			}
			var sum uint64
			for _, i := range parts[1:8] {
				v, err := strconv.ParseUint(i, 10, 64)
				if err != nil {
					return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
				}
				sum += v
			}
			return (sum * nanoSeconds) / s.clockTicks, nil
		}
	}
	return 0, fmt.Errorf("invalid stat format")
}
<commit_msg>statsCollector: fix data race in run()<commit_after>package daemon

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/docker/daemon/execdriver"
	"github.com/docker/docker/pkg/pubsub"
	"github.com/docker/libcontainer/system"
)

// newStatsCollector returns a new statsCollector that collections
// network and cgroup stats for a registered container at the specified
// interval.  The collector allows non-running containers to be added
// and will start processing stats when they are started.
func newStatsCollector(interval time.Duration) *statsCollector {
	s := &statsCollector{
		interval:   interval,
		publishers: make(map[*Container]*pubsub.Publisher),
		clockTicks: uint64(system.GetClockTicks()),
	}
	go s.run()
	return s
}

// statsCollector manages and provides container resource stats
type statsCollector struct {
	m          sync.Mutex
	interval   time.Duration
	clockTicks uint64
	publishers map[*Container]*pubsub.Publisher
}

// collect registers the container with the collector and adds it to
// the event loop for collection on the specified interval returning
// a channel for the subscriber to receive on.
func (s *statsCollector) collect(c *Container) chan interface{} {
	s.m.Lock()
	defer s.m.Unlock()
	publisher, exists := s.publishers[c]
	if !exists {
		publisher = pubsub.NewPublisher(100*time.Millisecond, 1024)
		s.publishers[c] = publisher
	}
	return publisher.Subscribe()
}

// stopCollection closes the channels for all subscribers and removes
// the container from metrics collection.
func (s *statsCollector) stopCollection(c *Container) {
	s.m.Lock()
	if publisher, exists := s.publishers[c]; exists {
		publisher.Close()
		delete(s.publishers, c)
	}
	s.m.Unlock()
}

// unsubscribe removes a specific subscriber from receiving updates for a container's stats.
func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) {
	s.m.Lock()
	publisher := s.publishers[c]
	if publisher != nil {
		publisher.Evict(ch)
		if publisher.Len() == 0 {
			delete(s.publishers, c)
		}
	}
	s.m.Unlock()
}

func (s *statsCollector) run() {
	type publishersPair struct {
		container *Container
		publisher *pubsub.Publisher
	}
	// we cannot determine the capacity here.
	// it will grow enough in first iteration
	var pairs []publishersPair

	for range time.Tick(s.interval) {
		systemUsage, err := s.getSystemCpuUsage()
		if err != nil {
			logrus.Errorf("collecting system cpu usage: %v", err)
			continue
		}

		// it does not make sense in the first iteration,
		// but saves allocations in further iterations
		pairs = pairs[:0]

		s.m.Lock()
		for container, publisher := range s.publishers {
			// copy pointers here to release the lock ASAP
			pairs = append(pairs, publishersPair{container, publisher})
		}
		s.m.Unlock()

		for _, pair := range pairs {
			stats, err := pair.container.Stats()
			if err != nil {
				if err != execdriver.ErrNotRunning {
					logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
				}
				continue
			}
			stats.SystemUsage = systemUsage
			pair.publisher.Publish(stats)
		}
	}
}

const nanoSeconds = 1e9

// getSystemCpuUSage returns the host system's cpu usage in nanoseconds
// for the system to match the cgroup readings are returned in the same format.
func (s *statsCollector) getSystemCpuUsage() (uint64, error) {
	f, err := os.Open("/proc/stat")
	if err != nil {
		return 0, err
	}
	defer f.Close()
	sc := bufio.NewScanner(f)
	for sc.Scan() {
		parts := strings.Fields(sc.Text())
		switch parts[0] {
		case "cpu":
			if len(parts) < 8 {
				return 0, fmt.Errorf("invalid number of cpu fields")
			}
			var sum uint64
			for _, i := range parts[1:8] {
				v, err := strconv.ParseUint(i, 10, 64)
				if err != nil {
					return 0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
				}
				sum += v
			}
			return (sum * nanoSeconds) / s.clockTicks, nil
		}
	}
	return 0, fmt.Errorf("invalid stat format")
}
<|endoftext|>


<commit_before>package docker

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/dotcloud/docker/utils"
	"io"
	"io/ioutil"
	"os"
	"path"
	"reflect"
	"strings"
)

type BuildFile interface {
	Build(io.Reader, io.Reader) (string, error)
	CmdFrom(string) error
	CmdRun(string) error
}

type buildFile struct {
	runtime *Runtime
	builder *Builder
	srv     *Server

	image      string
	maintainer string
	config     *Config
	context    string

	tmpContainers map[string]struct{}
	tmpImages     map[string]struct{}

	out io.Writer
}

func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
	for c := range containers {
		tmp := b.runtime.Get(c)
		b.runtime.Destroy(tmp)
		utils.Debugf("Removing container %s", c)
	}
	for i := range images {
		b.runtime.graph.Delete(i)
		utils.Debugf("Removing image %s", i)
	}
}

func (b *buildFile) CmdFrom(name string) error {
	image, err := b.runtime.repositories.LookupImage(name)
	if err != nil {
		if b.runtime.graph.IsNotExist(err) {

			var tag, remote string
			if strings.Contains(name, ":") {
				remoteParts := strings.Split(name, ":")
				tag = remoteParts[1]
				remote = remoteParts[0]
			} else {
				remote = name
			}

			if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
				return err
			}

			image, err = b.runtime.repositories.LookupImage(name)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}
	b.image = image.Id
	b.config = &Config{}
	return nil
}

func (b *buildFile) CmdMaintainer(name string) error {
	b.maintainer = name
	return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
}

func (b *buildFile) CmdRun(args string) error {
	if b.image == "" {
		return fmt.Errorf("Please provide a source image with `from` prior to run")
	}
	config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
	if err != nil {
		return err
	}

	cmd := b.config.Cmd
	b.config.Cmd = nil
	MergeConfig(b.config, config)

	utils.Debugf("Command to be executed: %v", b.config.Cmd)

	if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
		return err
	} else if cache != nil {
		utils.Debugf("[BUILDER] Use cached version")
		b.image = cache.Id
		return nil
	} else {
		utils.Debugf("[BUILDER] Cache miss")
	}

	cid, err := b.run()
	if err != nil {
		return err
	}
	if err := b.commit(cid, cmd, "run"); err != nil {
		return err
	}
	b.config.Cmd = cmd
	return nil
}

func (b *buildFile) CmdEnv(args string) error {
	tmp := strings.SplitN(args, " ", 2)
	if len(tmp) != 2 {
		return fmt.Errorf("Invalid ENV format")
	}
	key := strings.Trim(tmp[0], " ")
	value := strings.Trim(tmp[1], " ")

	for i, elem := range b.config.Env {
		if strings.HasPrefix(elem, key+"=") {
			b.config.Env[i] = key + "=" + value
			return nil
		}
	}
	b.config.Env = append(b.config.Env, key+"="+value)
	return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
}

func (b *buildFile) CmdCmd(args string) error {
	var cmd []string
	if err := json.Unmarshal([]byte(args), &cmd); err != nil {
		utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
		cmd = []string{"/bin/sh", "-c", args}
	}
	if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
		return err
	}
	b.config.Cmd = cmd
	return nil
}

func (b *buildFile) CmdExpose(args string) error {
	ports := strings.Split(args, " ")
	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
	return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
}

func (b *buildFile) CmdInsert(args string) error {
	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}

func (b *buildFile) CmdCopy(args string) error {
	return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}

func (b *buildFile) CmdAdd(args string) error {
	if b.context == "" {
		return fmt.Errorf("No context given. Impossible to use ADD")
	}
	tmp := strings.SplitN(args, " ", 2)
	if len(tmp) != 2 {
		return fmt.Errorf("Invalid ADD format")
	}
	orig := strings.Trim(tmp[0], " ")
	dest := strings.Trim(tmp[1], " ")

	cmd := b.config.Cmd
	b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
	cid, err := b.run()
	if err != nil {
		return err
	}

	container := b.runtime.Get(cid)
	if container == nil {
		return fmt.Errorf("Error while creating the container (CmdAdd)")
	}
	if err := container.EnsureMounted(); err != nil {
		return err
	}
	defer container.Unmount()

	origPath := path.Join(b.context, orig)
	destPath := path.Join(container.RootfsPath(), dest)

	fi, err := os.Stat(origPath)
	if err != nil {
		return err
	}
	if fi.IsDir() {
		if err := os.MkdirAll(destPath, 0700); err != nil {
			return err
		}

		files, err := ioutil.ReadDir(path.Join(b.context, orig))
		if err != nil {
			return err
		}
		for _, fi := range files {
			if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
				return err
			}
		}
	} else {
		if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
			return err
		}
		if err := utils.CopyDirectory(origPath, destPath); err != nil {
			return err
		}
	}
	if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
		return err
	}
	b.config.Cmd = cmd
	return nil
}

func (b *buildFile) run() (string, error) {
	if b.image == "" {
		return "", fmt.Errorf("Please provide a source image with `from` prior to run")
	}
	b.config.Image = b.image

	// Create the container and start it
	c, err := b.builder.Create(b.config)
	if err != nil {
		return "", err
	}
	b.tmpContainers[c.Id] = struct{}{}

	//start the container
	if err := c.Start(); err != nil {
		return "", err
	}

	// Wait for it to finish
	if ret := c.Wait(); ret != 0 {
		return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
	}

	return c.Id, nil
}

// Commit the container <id> with the autorun command <autoCmd>
func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
	if b.image == "" {
		return fmt.Errorf("Please provide a source image with `from` prior to commit")
	}
	b.config.Image = b.image
	if id == "" {
		b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}

		if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
			return err
		} else if cache != nil {
			utils.Debugf("[BUILDER] Use cached version")
			b.image = cache.Id
			return nil
		} else {
			utils.Debugf("[BUILDER] Cache miss")
		}

		if cid, err := b.run(); err != nil {
			return err
		} else {
			id = cid
		}
	}

	container := b.runtime.Get(id)
	if container == nil {
		return fmt.Errorf("An error occured while creating the container")
	}

	// Note: Actually copy the struct
	autoConfig := *b.config
	autoConfig.Cmd = autoCmd
	// Commit the container
	image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
	if err != nil {
		return err
	}
	b.tmpImages[image.Id] = struct{}{}
	b.image = image.Id
	return nil
}

func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
	defer b.clearTmp(b.tmpContainers, b.tmpImages)

	if context != nil {
		name, err := ioutil.TempDir("/tmp", "docker-build")
		if err != nil {
			return "", err
		}
		if err := Untar(context, name); err != nil {
			return "", err
		}
		defer os.RemoveAll(name)
		b.context = name
	}
	file := bufio.NewReader(dockerfile)
	for {
		line, err := file.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			return "", err
		}
		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
		// Skip comments and empty line
		if len(line) == 0 || line[0] == '#' {
			continue
		}
		tmp := strings.SplitN(line, " ", 2)
		if len(tmp) != 2 {
			return "", fmt.Errorf("Invalid Dockerfile format")
		}
		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
		arguments := strings.Trim(tmp[1], " ")

		fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)

		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
		if !exists {
			fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
			continue
		}
		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
		if ret != nil {
			return "", ret.(error)
		}

		fmt.Fprintf(b.out, "===> %v\n", b.image)
	}
	if b.image != "" {
		fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
		return b.image, nil
	}
	for i := range b.tmpContainers {
		delete(b.tmpContainers, i)
	}
	return "", fmt.Errorf("An error occured during the build\n")
}

func NewBuildFile(srv *Server, out io.Writer) BuildFile {
	return &buildFile{
		builder:       NewBuilder(srv.runtime),
		runtime:       srv.runtime,
		srv:           srv,
		config:        &Config{},
		out:           out,
		tmpContainers: make(map[string]struct{}),
		tmpImages:     make(map[string]struct{}),
	}
}
<commit_msg>* Build: never remove temporary images and containers<commit_after>package docker

import (
	"bufio"
	"encoding/json"
	"fmt"
	"github.com/dotcloud/docker/utils"
	"io"
	"io/ioutil"
	"os"
	"path"
	"reflect"
	"strings"
)

type BuildFile interface {
	Build(io.Reader, io.Reader) (string, error)
	CmdFrom(string) error
	CmdRun(string) error
}

type buildFile struct {
	runtime *Runtime
	builder *Builder
	srv     *Server

	image      string
	maintainer string
	config     *Config
	context    string

	tmpContainers map[string]struct{}
	tmpImages     map[string]struct{}

	out io.Writer
}

func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
	for c := range containers {
		tmp := b.runtime.Get(c)
		b.runtime.Destroy(tmp)
		utils.Debugf("Removing container %s", c)
	}
	for i := range images {
		b.runtime.graph.Delete(i)
		utils.Debugf("Removing image %s", i)
	}
}

func (b *buildFile) CmdFrom(name string) error {
	image, err := b.runtime.repositories.LookupImage(name)
	if err != nil {
		if b.runtime.graph.IsNotExist(err) {

			var tag, remote string
			if strings.Contains(name, ":") {
				remoteParts := strings.Split(name, ":")
				tag = remoteParts[1]
				remote = remoteParts[0]
			} else {
				remote = name
			}

			if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
				return err
			}

			image, err = b.runtime.repositories.LookupImage(name)
			if err != nil {
				return err
			}
		} else {
			return err
		}
	}
	b.image = image.Id
	b.config = &Config{}
	return nil
}

func (b *buildFile) CmdMaintainer(name string) error {
	b.maintainer = name
	return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
}

func (b *buildFile) CmdRun(args string) error {
	if b.image == "" {
		return fmt.Errorf("Please provide a source image with `from` prior to run")
	}
	config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
	if err != nil {
		return err
	}

	cmd := b.config.Cmd
	b.config.Cmd = nil
	MergeConfig(b.config, config)

	utils.Debugf("Command to be executed: %v", b.config.Cmd)

	if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
		return err
	} else if cache != nil {
		utils.Debugf("[BUILDER] Use cached version")
		b.image = cache.Id
		return nil
	} else {
		utils.Debugf("[BUILDER] Cache miss")
	}

	cid, err := b.run()
	if err != nil {
		return err
	}
	if err := b.commit(cid, cmd, "run"); err != nil {
		return err
	}
	b.config.Cmd = cmd
	return nil
}

func (b *buildFile) CmdEnv(args string) error {
	tmp := strings.SplitN(args, " ", 2)
	if len(tmp) != 2 {
		return fmt.Errorf("Invalid ENV format")
	}
	key := strings.Trim(tmp[0], " ")
	value := strings.Trim(tmp[1], " ")

	for i, elem := range b.config.Env {
		if strings.HasPrefix(elem, key+"=") {
			b.config.Env[i] = key + "=" + value
			return nil
		}
	}
	b.config.Env = append(b.config.Env, key+"="+value)
	return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
}

func (b *buildFile) CmdCmd(args string) error {
	var cmd []string
	if err := json.Unmarshal([]byte(args), &cmd); err != nil {
		utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
		cmd = []string{"/bin/sh", "-c", args}
	}
	if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
		return err
	}
	b.config.Cmd = cmd
	return nil
}

func (b *buildFile) CmdExpose(args string) error {
	ports := strings.Split(args, " ")
	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
	return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
}

func (b *buildFile) CmdInsert(args string) error {
	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}

func (b *buildFile) CmdCopy(args string) error {
	return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}

func (b *buildFile) CmdAdd(args string) error {
	if b.context == "" {
		return fmt.Errorf("No context given. Impossible to use ADD")
	}
	tmp := strings.SplitN(args, " ", 2)
	if len(tmp) != 2 {
		return fmt.Errorf("Invalid ADD format")
	}
	orig := strings.Trim(tmp[0], " ")
	dest := strings.Trim(tmp[1], " ")

	cmd := b.config.Cmd
	b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
	cid, err := b.run()
	if err != nil {
		return err
	}

	container := b.runtime.Get(cid)
	if container == nil {
		return fmt.Errorf("Error while creating the container (CmdAdd)")
	}
	if err := container.EnsureMounted(); err != nil {
		return err
	}
	defer container.Unmount()

	origPath := path.Join(b.context, orig)
	destPath := path.Join(container.RootfsPath(), dest)

	fi, err := os.Stat(origPath)
	if err != nil {
		return err
	}
	if fi.IsDir() {
		if err := os.MkdirAll(destPath, 0700); err != nil {
			return err
		}

		files, err := ioutil.ReadDir(path.Join(b.context, orig))
		if err != nil {
			return err
		}
		for _, fi := range files {
			if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
				return err
			}
		}
	} else {
		if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
			return err
		}
		if err := utils.CopyDirectory(origPath, destPath); err != nil {
			return err
		}
	}
	if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
		return err
	}
	b.config.Cmd = cmd
	return nil
}

func (b *buildFile) run() (string, error) {
	if b.image == "" {
		return "", fmt.Errorf("Please provide a source image with `from` prior to run")
	}
	b.config.Image = b.image

	// Create the container and start it
	c, err := b.builder.Create(b.config)
	if err != nil {
		return "", err
	}
	b.tmpContainers[c.Id] = struct{}{}

	//start the container
	if err := c.Start(); err != nil {
		return "", err
	}

	// Wait for it to finish
	if ret := c.Wait(); ret != 0 {
		return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
	}

	return c.Id, nil
}

// Commit the container <id> with the autorun command <autoCmd>
func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
	if b.image == "" {
		return fmt.Errorf("Please provide a source image with `from` prior to commit")
	}
	b.config.Image = b.image
	if id == "" {
		b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}

		if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
			return err
		} else if cache != nil {
			utils.Debugf("[BUILDER] Use cached version")
			b.image = cache.Id
			return nil
		} else {
			utils.Debugf("[BUILDER] Cache miss")
		}

		if cid, err := b.run(); err != nil {
			return err
		} else {
			id = cid
		}
	}

	container := b.runtime.Get(id)
	if container == nil {
		return fmt.Errorf("An error occured while creating the container")
	}

	// Note: Actually copy the struct
	autoConfig := *b.config
	autoConfig.Cmd = autoCmd
	// Commit the container
	image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
	if err != nil {
		return err
	}
	b.tmpImages[image.Id] = struct{}{}
	b.image = image.Id
	return nil
}

func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
	if context != nil {
		name, err := ioutil.TempDir("/tmp", "docker-build")
		if err != nil {
			return "", err
		}
		if err := Untar(context, name); err != nil {
			return "", err
		}
		defer os.RemoveAll(name)
		b.context = name
	}
	file := bufio.NewReader(dockerfile)
	for {
		line, err := file.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				break
			}
			return "", err
		}
		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
		// Skip comments and empty line
		if len(line) == 0 || line[0] == '#' {
			continue
		}
		tmp := strings.SplitN(line, " ", 2)
		if len(tmp) != 2 {
			return "", fmt.Errorf("Invalid Dockerfile format")
		}
		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
		arguments := strings.Trim(tmp[1], " ")

		fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)

		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
		if !exists {
			fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
			continue
		}
		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
		if ret != nil {
			return "", ret.(error)
		}

		fmt.Fprintf(b.out, "===> %v\n", b.image)
	}
	if b.image != "" {
		fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
		return b.image, nil
	}
	return "", fmt.Errorf("An error occured during the build\n")
}

func NewBuildFile(srv *Server, out io.Writer) BuildFile {
	return &buildFile{
		builder:       NewBuilder(srv.runtime),
		runtime:       srv.runtime,
		srv:           srv,
		config:        &Config{},
		out:           out,
		tmpContainers: make(map[string]struct{}),
		tmpImages:     make(map[string]struct{}),
	}
}
<|endoftext|>


<commit_before>package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: 2021-11-12 06:58:39.521675 +0000 UTC

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "0084fcb65c3ac15e1a0e9386550f5cf98e900231"

// SpartaGitShortHash is the short version of SpartaGitHash
const SpartaGitShortHash = "0084fcb"
<commit_msg>"Autogenerated build info"<commit_after>package sparta

// THIS FILE IS AUTOMATICALLY GENERATED
// DO NOT EDIT
// CREATED: 2021-11-12 07:00:41.767803 +0000 UTC

// SpartaGitHash is the commit hash of this Sparta library
const SpartaGitHash = "882652d00dd8f0ea77b47a5009e816472ca130e7"

// SpartaGitShortHash is the short version of SpartaGitHash
const SpartaGitShortHash = "882652d"
<|endoftext|>


<commit_before>package slackapi

import (
	"encoding/json"
	"testing"
)

func CheckResponse(t *testing.T, x interface{}, y string) {
	out, err := json.Marshal(x)
	if err != nil {
		t.Fatal("json fromat;", err)
	}
	if string(out) != y {
		t.Fatalf("invalid json response;\n- %s\n+ %s\n", y, out)
	}
}

func TestAPITest(t *testing.T) {
	s := New()
	x := s.APITest()
	y := `{"ok":true}`
	CheckResponse(t, x, y)
}

func TestAppsList(t *testing.T) {
	s := New()
	x := s.AppsList()
	y := `{"ok":false,"error":"not_authed","apps":null,"cache_ts":""}`
	CheckResponse(t, x, y)
}

func TestAuthRevoke(t *testing.T) {
	s := New()
	x := s.AuthRevoke()
	y := `{"ok":false,"error":"not_authed","revoked":false}`
	CheckResponse(t, x, y)
}

func TestAuthTest(t *testing.T) {
	s := New()
	x, err := s.AuthTest()
	if err != nil {
		t.Fatal(err)
	}
	y := `{"ok":false,"error":"not_authed","team":"","team_id":"","url":"","user":"","user_id":""}`
	CheckResponse(t, x, y)
}

func TestBotsInfo(t *testing.T) {
	s := New()
	x := s.BotsInfo("user")
	y := `{"ok":false,"error":"not_authed","bot":{"id":"","deleted":false,"name":"","icons":null}}`
	CheckResponse(t, x, y)
}

func TestChannelsID(t *testing.T) {
	s := New()
	x := s.ChannelsID("channel")
	y := `"channel"`
	CheckResponse(t, x, y)
}

func TestChannelsMyHistory(t *testing.T) {
	s := New()
	x := s.ChannelsMyHistory("channel", "1234567890")
	y := `{"Filtered":0,"Latest":"","Messages":null,"Oldest":"","Total":0,"Username":""}`
	CheckResponse(t, x, y)
}

func TestChannelsPurgeHistory(t *testing.T) {
	s := New()
	x := s.ChannelsPurgeHistory("channel", "1234567890", true)
	y := `{"Deleted":0,"NotDeleted":0,"Messages":null}`
	CheckResponse(t, x, y)
}

func TestChannelsSetRetention(t *testing.T) {
	s := New()
	x := s.ChannelsSetRetention("channel", 1)
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestChannelsSuggestions(t *testing.T) {
	s := New()
	x := s.ChannelsSuggestions()
	y := `{"ok":false,"error":"not_authed","status":{"ok":false},"suggestion_types_tried":null}`
	CheckResponse(t, x, y)
}

func TestChatDelete(t *testing.T) {
	s := New()
	x := s.ChatDelete(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatMeMessage(t *testing.T) {
	s := New()
	x := s.ChatMeMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatPostMessage(t *testing.T) {
	s := New()
	x := s.ChatPostMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestChatUpdate(t *testing.T) {
	s := New()
	x := s.ChatUpdate(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestConversationsArchive(t *testing.T) {
	s := New()
	x := s.ConversationsArchive("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsCreate(t *testing.T) {
	s := New()
	x := s.ConversationsCreate("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsHistory(t *testing.T) {
	s := New()
	x := s.ConversationsHistory(ConversationsHistoryInput{Channel: "channel", Latest: "1234567890"})
	y := `{"ok":false,"error":"not_authed","messages":null,"has_more":false,"pin_count":0,"unread_count_display":0,"response_metadata":{"next_cursor":""}}`
	CheckResponse(t, x, y)
}

func TestConversationsInfo(t *testing.T) {
	s := New()
	x := s.ConversationsInfo("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsJoin(t *testing.T) {
	s := New()
	x := s.ConversationsJoin("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsInvite(t *testing.T) {
	s := New()
	x := s.ConversationsInvite("channel", "user1", "user2", "user3")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsKick(t *testing.T) {
	s := New()
	x := s.ConversationsKick("channel", "user")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsLeave(t *testing.T) {
	s := New()
	x := s.ConversationsLeave("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsList(t *testing.T) {
	s := New()
	x := s.ConversationsList(ConversationsListInput{})
	y := `{"ok":false,"error":"not_authed","channels":null}`
	CheckResponse(t, x, y)
}

func TestConversationsRename(t *testing.T) {
	s := New()
	x := s.ConversationsRename("channel", "lennahc")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsReplies(t *testing.T) {
	s := New()
	x := s.ConversationsReplies(ConversationsRepliesInput{Channel: "general", Timestamp: "1234567890.123456"})
	y := `{"ok":false,"error":"not_authed","messages":null,"has_more":false,"pin_count":0,"unread_count_display":0,"response_metadata":{"next_cursor":""}}`
	CheckResponse(t, x, y)
}

func TestConversationsSetPurpose(t *testing.T) {
	s := New()
	x := s.ConversationsSetPurpose("channel", "purpose")
	y := `{"ok":false,"error":"not_authed","purpose":""}`
	CheckResponse(t, x, y)
}

func TestConversationsSetTopic(t *testing.T) {
	s := New()
	x := s.ConversationsSetTopic("channel", "topic")
	y := `{"ok":false,"error":"not_authed","topic":""}`
	CheckResponse(t, x, y)
}

func TestConversationsUnarchive(t *testing.T) {
	s := New()
	x := s.ConversationsUnarchive("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestDNDEndDnd(t *testing.T) {
	s := New()
	x := s.DNDEndDnd()
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestDNDEndSnooze(t *testing.T) {
	s := New()
	x := s.DNDEndSnooze()
	y := `{"ok":false,"error":"not_authed","dnd_enabled":false,"next_dnd_start_ts":0,"next_dnd_end_ts":0,"snooze_debug":{}}`
	CheckResponse(t, x, y)
}

func TestDNDInfo(t *testing.T) {
	s := New()
	x := s.DNDInfo("admin")
	y := `{"ok":false,"error":"not_authed","dnd_enabled":false,"next_dnd_start_ts":0,"next_dnd_end_ts":0,"snooze_debug":{}}`
	CheckResponse(t, x, y)
}

func TestDNDSetSnooze(t *testing.T) {
	s := New()
	x := s.DNDSetSnooze(60)
	y := `{"ok":false,"error":"not_authed","snooze_debug":{}}`
	CheckResponse(t, x, y)
}

func TestDNDTeamInfo(t *testing.T) {
	s := New()
	x := s.DNDTeamInfo("admin")
	y := `{"ok":false,"error":"not_authed","cached":false,"users":null}`
	CheckResponse(t, x, y)
}

func TestEmojiList(t *testing.T) {
	s := New()
	x := s.EmojiList()
	y := `{"ok":false,"error":"not_authed","cache_ts":"","emoji":null}`
	CheckResponse(t, x, y)
}

func TestFilesCommentsAdd(t *testing.T) {
	s := New()
	x := s.FilesCommentsAdd("fileid", "comment")
	y := `{"ok":false,"error":"unknown_method","comment":{"comment":"","id":"","user":"","created":0,"timestamp":0,"is_intro":false}}`
	CheckResponse(t, x, y)
}

func TestFilesCommentsDelete(t *testing.T) {
	s := New()
	x := s.FilesCommentsDelete("fileid", "commentid")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestFilesCommentsEdit(t *testing.T) {
	s := New()
	x := s.FilesCommentsEdit("fileid", "commentid", "comment")
	y := `{"ok":false,"error":"unknown_method","comment":{"comment":"","id":"","user":"","created":0,"timestamp":0,"is_intro":false}}`
	CheckResponse(t, x, y)
}

func TestFilesDelete(t *testing.T) {
	s := New()
	x := s.FilesDelete("fileid")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestFilesInfo(t *testing.T) {
	s := New()
	x := s.FilesInfo("fileid", 1, 1)
	y := `{"ok":false,"error":"not_authed","file":{"channels":null,"comments_count":0,"created":0,"deanimate_gif":"","edit_link":"","external_type":"","filetype":"","groups":null,"id":"","image_exif_rotation":0,"initial_comment":{"comment":"","id":"","user":"","created":0,"timestamp":0,"is_intro":false},"ims":null,"lines":0,"lines_more":0,"mimetype":"","mode":"","name":"","num_stars":0,"original_h":0,"original_w":0,"permalink":"","permalink_public":"","pretty_type":"","preview":"","preview_highlight":"","reactions":null,"score":"","size":0,"thumb_160":"","thumb_360":"","thumb_360_gif":"","thumb_360_h":0,"thumb_360_w":0,"thumb_480":"","thumb_480_gif":"","thumb_480_h":0,"thumb_480_w":0,"thumb_64":"","thumb_80":"","timestamp":0,"title":"","url":"","url_download":"","url_private":"","url_private_download":"","user":"","username":"","display_as_bot":false,"editable":false,"is_external":false,"is_public":false,"is_starred":false,"public_url_shared":false,"top_file":false},"comments":null,"paging":{"count":0,"page":0,"pages":0,"total":0}}`
	CheckResponse(t, x, y)
}

func TestFilesList(t *testing.T) {
	s := New()
	x := s.FilesList(FileListArgs{})
	y := `{"ok":false,"error":"not_authed","files":null,"paging":{"count":0,"page":0,"pages":0,"total":0}}`
	CheckResponse(t, x, y)
}
<commit_msg>Remove files.list unit test due to API uncertanties<commit_after>package slackapi

import (
	"encoding/json"
	"testing"
)

func CheckResponse(t *testing.T, x interface{}, y string) {
	out, err := json.Marshal(x)
	if err != nil {
		t.Fatal("json fromat;", err)
	}
	if string(out) != y {
		t.Fatalf("invalid json response;\n- %s\n+ %s\n", y, out)
	}
}

func TestAPITest(t *testing.T) {
	s := New()
	x := s.APITest()
	y := `{"ok":true}`
	CheckResponse(t, x, y)
}

func TestAppsList(t *testing.T) {
	s := New()
	x := s.AppsList()
	y := `{"ok":false,"error":"not_authed","apps":null,"cache_ts":""}`
	CheckResponse(t, x, y)
}

func TestAuthRevoke(t *testing.T) {
	s := New()
	x := s.AuthRevoke()
	y := `{"ok":false,"error":"not_authed","revoked":false}`
	CheckResponse(t, x, y)
}

func TestAuthTest(t *testing.T) {
	s := New()
	x, err := s.AuthTest()
	if err != nil {
		t.Fatal(err)
	}
	y := `{"ok":false,"error":"not_authed","team":"","team_id":"","url":"","user":"","user_id":""}`
	CheckResponse(t, x, y)
}

func TestBotsInfo(t *testing.T) {
	s := New()
	x := s.BotsInfo("user")
	y := `{"ok":false,"error":"not_authed","bot":{"id":"","deleted":false,"name":"","icons":null}}`
	CheckResponse(t, x, y)
}

func TestChannelsID(t *testing.T) {
	s := New()
	x := s.ChannelsID("channel")
	y := `"channel"`
	CheckResponse(t, x, y)
}

func TestChannelsMyHistory(t *testing.T) {
	s := New()
	x := s.ChannelsMyHistory("channel", "1234567890")
	y := `{"Filtered":0,"Latest":"","Messages":null,"Oldest":"","Total":0,"Username":""}`
	CheckResponse(t, x, y)
}

func TestChannelsPurgeHistory(t *testing.T) {
	s := New()
	x := s.ChannelsPurgeHistory("channel", "1234567890", true)
	y := `{"Deleted":0,"NotDeleted":0,"Messages":null}`
	CheckResponse(t, x, y)
}

func TestChannelsSetRetention(t *testing.T) {
	s := New()
	x := s.ChannelsSetRetention("channel", 1)
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestChannelsSuggestions(t *testing.T) {
	s := New()
	x := s.ChannelsSuggestions()
	y := `{"ok":false,"error":"not_authed","status":{"ok":false},"suggestion_types_tried":null}`
	CheckResponse(t, x, y)
}

func TestChatDelete(t *testing.T) {
	s := New()
	x := s.ChatDelete(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatMeMessage(t *testing.T) {
	s := New()
	x := s.ChatMeMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","text":"","ts":""}`
	CheckResponse(t, x, y)
}

func TestChatPostMessage(t *testing.T) {
	s := New()
	x := s.ChatPostMessage(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestChatUpdate(t *testing.T) {
	s := New()
	x := s.ChatUpdate(MessageArgs{})
	y := `{"ok":false,"error":"not_authed","channel":"","ts":"","message":{"display_as_bot":false}}`
	CheckResponse(t, x, y)
}

func TestConversationsArchive(t *testing.T) {
	s := New()
	x := s.ConversationsArchive("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsCreate(t *testing.T) {
	s := New()
	x := s.ConversationsCreate("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsHistory(t *testing.T) {
	s := New()
	x := s.ConversationsHistory(ConversationsHistoryInput{Channel: "channel", Latest: "1234567890"})
	y := `{"ok":false,"error":"not_authed","messages":null,"has_more":false,"pin_count":0,"unread_count_display":0,"response_metadata":{"next_cursor":""}}`
	CheckResponse(t, x, y)
}

func TestConversationsInfo(t *testing.T) {
	s := New()
	x := s.ConversationsInfo("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsJoin(t *testing.T) {
	s := New()
	x := s.ConversationsJoin("channel")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsInvite(t *testing.T) {
	s := New()
	x := s.ConversationsInvite("channel", "user1", "user2", "user3")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsKick(t *testing.T) {
	s := New()
	x := s.ConversationsKick("channel", "user")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsLeave(t *testing.T) {
	s := New()
	x := s.ConversationsLeave("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestConversationsList(t *testing.T) {
	s := New()
	x := s.ConversationsList(ConversationsListInput{})
	y := `{"ok":false,"error":"not_authed","channels":null}`
	CheckResponse(t, x, y)
}

func TestConversationsRename(t *testing.T) {
	s := New()
	x := s.ConversationsRename("channel", "lennahc")
	y := `{"ok":false,"error":"not_authed","channel":{"created":0,"creator":"","id":"","is_archived":false,"is_channel":false,"is_general":false,"is_group":false,"is_member":false,"is_mpim":false,"is_open":false,"last_read":"","latest":{"text":"","ts":"","type":"","user":""},"members":null,"name":"","name_normalized":"","num_members":0,"purpose":{"creator":"","last_set":0,"value":""},"topic":{"creator":"","last_set":0,"value":""},"unread_count":0,"unread_count_display":0}}`
	CheckResponse(t, x, y)
}

func TestConversationsReplies(t *testing.T) {
	s := New()
	x := s.ConversationsReplies(ConversationsRepliesInput{Channel: "general", Timestamp: "1234567890.123456"})
	y := `{"ok":false,"error":"not_authed","messages":null,"has_more":false,"pin_count":0,"unread_count_display":0,"response_metadata":{"next_cursor":""}}`
	CheckResponse(t, x, y)
}

func TestConversationsSetPurpose(t *testing.T) {
	s := New()
	x := s.ConversationsSetPurpose("channel", "purpose")
	y := `{"ok":false,"error":"not_authed","purpose":""}`
	CheckResponse(t, x, y)
}

func TestConversationsSetTopic(t *testing.T) {
	s := New()
	x := s.ConversationsSetTopic("channel", "topic")
	y := `{"ok":false,"error":"not_authed","topic":""}`
	CheckResponse(t, x, y)
}

func TestConversationsUnarchive(t *testing.T) {
	s := New()
	x := s.ConversationsUnarchive("channel")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestDNDEndDnd(t *testing.T) {
	s := New()
	x := s.DNDEndDnd()
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestDNDEndSnooze(t *testing.T) {
	s := New()
	x := s.DNDEndSnooze()
	y := `{"ok":false,"error":"not_authed","dnd_enabled":false,"next_dnd_start_ts":0,"next_dnd_end_ts":0,"snooze_debug":{}}`
	CheckResponse(t, x, y)
}

func TestDNDInfo(t *testing.T) {
	s := New()
	x := s.DNDInfo("admin")
	y := `{"ok":false,"error":"not_authed","dnd_enabled":false,"next_dnd_start_ts":0,"next_dnd_end_ts":0,"snooze_debug":{}}`
	CheckResponse(t, x, y)
}

func TestDNDSetSnooze(t *testing.T) {
	s := New()
	x := s.DNDSetSnooze(60)
	y := `{"ok":false,"error":"not_authed","snooze_debug":{}}`
	CheckResponse(t, x, y)
}

func TestDNDTeamInfo(t *testing.T) {
	s := New()
	x := s.DNDTeamInfo("admin")
	y := `{"ok":false,"error":"not_authed","cached":false,"users":null}`
	CheckResponse(t, x, y)
}

func TestEmojiList(t *testing.T) {
	s := New()
	x := s.EmojiList()
	y := `{"ok":false,"error":"not_authed","cache_ts":"","emoji":null}`
	CheckResponse(t, x, y)
}

func TestFilesCommentsAdd(t *testing.T) {
	s := New()
	x := s.FilesCommentsAdd("fileid", "comment")
	y := `{"ok":false,"error":"unknown_method","comment":{"comment":"","id":"","user":"","created":0,"timestamp":0,"is_intro":false}}`
	CheckResponse(t, x, y)
}

func TestFilesCommentsDelete(t *testing.T) {
	s := New()
	x := s.FilesCommentsDelete("fileid", "commentid")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestFilesCommentsEdit(t *testing.T) {
	s := New()
	x := s.FilesCommentsEdit("fileid", "commentid", "comment")
	y := `{"ok":false,"error":"unknown_method","comment":{"comment":"","id":"","user":"","created":0,"timestamp":0,"is_intro":false}}`
	CheckResponse(t, x, y)
}

func TestFilesDelete(t *testing.T) {
	s := New()
	x := s.FilesDelete("fileid")
	y := `{"ok":false,"error":"not_authed"}`
	CheckResponse(t, x, y)
}

func TestFilesInfo(t *testing.T) {
	s := New()
	x := s.FilesInfo("fileid", 1, 1)
	y := `{"ok":false,"error":"not_authed","file":{"channels":null,"comments_count":0,"created":0,"deanimate_gif":"","edit_link":"","external_type":"","filetype":"","groups":null,"id":"","image_exif_rotation":0,"initial_comment":{"comment":"","id":"","user":"","created":0,"timestamp":0,"is_intro":false},"ims":null,"lines":0,"lines_more":0,"mimetype":"","mode":"","name":"","num_stars":0,"original_h":0,"original_w":0,"permalink":"","permalink_public":"","pretty_type":"","preview":"","preview_highlight":"","reactions":null,"score":"","size":0,"thumb_160":"","thumb_360":"","thumb_360_gif":"","thumb_360_h":0,"thumb_360_w":0,"thumb_480":"","thumb_480_gif":"","thumb_480_h":0,"thumb_480_w":0,"thumb_64":"","thumb_80":"","timestamp":0,"title":"","url":"","url_download":"","url_private":"","url_private_download":"","user":"","username":"","display_as_bot":false,"editable":false,"is_external":false,"is_public":false,"is_starred":false,"public_url_shared":false,"top_file":false},"comments":null,"paging":{"count":0,"page":0,"pages":0,"total":0}}`
	CheckResponse(t, x, y)
}
<|endoftext|>


<commit_before>package softlayer

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/softlayer/softlayer-go/datatypes"
	"github.com/softlayer/softlayer-go/filter"
	"github.com/softlayer/softlayer-go/services"
	"github.com/softlayer/softlayer-go/session"

	"github.com/StackExchange/dnscontrol/v2/models"
	"github.com/StackExchange/dnscontrol/v2/providers"
	"github.com/StackExchange/dnscontrol/v2/providers/diff"
)

// SoftLayer is the protocol handle for this provider.
type SoftLayer struct {
	Session *session.Session
}

var features = providers.DocumentationNotes{
	providers.CanUseSRV:   providers.Can(),
	providers.CanGetZones: providers.Unimplemented(),
}

func init() {
	providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, features)
}

func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
	s := session.New(conf["username"], conf["api_key"], conf["endpoint_url"], conf["timeout"])

	if len(s.UserName) == 0 || len(s.APIKey) == 0 {
		return nil, fmt.Errorf("SoftLayer UserName and APIKey must be provided")
	}

	// s.Debug = true

	api := &SoftLayer{
		Session: s,
	}

	return api, nil
}

// GetNameservers returns the nameservers for a domain.
func (s *SoftLayer) GetNameservers(domain string) ([]*models.Nameserver, error) {
	// Always use the same nameservers for softlayer
	nservers := []string{"ns1.softlayer.com", "ns2.softlayer.com"}
	return models.StringsToNameservers(nservers), nil
}

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (client *SoftLayer) GetZoneRecords(domain string) (models.Records, error) {
	return nil, fmt.Errorf("not implemented")
	// This enables the get-zones subcommand.
	// Implement this by extracting the code from GetDomainCorrections into
	// a single function.  For most providers this should be relatively easy.
}

// GetDomainCorrections returns corrections to update a domain.
func (s *SoftLayer) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	corrections := []*models.Correction{}

	domain, err := s.getDomain(&dc.Name)

	if err != nil {
		return nil, err
	}

	actual, err := s.getExistingRecords(domain)

	if err != nil {
		return nil, err
	}

	_, create, delete, modify := diff.New(dc).IncrementalDiff(actual)

	for _, del := range delete {
		existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
		corrections = append(corrections, &models.Correction{
			Msg: del.String(),
			F:   s.deleteRecordFunc(*existing.Id),
		})
	}

	for _, cre := range create {
		corrections = append(corrections, &models.Correction{
			Msg: cre.String(),
			F:   s.createRecordFunc(cre.Desired, domain),
		})
	}

	for _, mod := range modify {
		existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
		corrections = append(corrections, &models.Correction{
			Msg: mod.String(),
			F:   s.updateRecordFunc(&existing, mod.Desired),
		})
	}

	return corrections, nil
}

func (s *SoftLayer) getDomain(name *string) (*datatypes.Dns_Domain, error) {
	domains, err := services.GetAccountService(s.Session).
		Filter(filter.Path("domains.name").Eq(name).Build()).
		Mask("resourceRecords").
		GetDomains()

	if err != nil {
		return nil, err
	}

	if len(domains) == 0 {
		return nil, fmt.Errorf("Didn't find a domain matching %s", *name)
	} else if len(domains) > 1 {
		return nil, fmt.Errorf("Found %d domains matching %s", len(domains), *name)
	}

	return &domains[0], nil
}

func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.RecordConfig, error) {
	actual := []*models.RecordConfig{}

	for _, record := range domain.ResourceRecords {
		recType := strings.ToUpper(*record.Type)

		if recType == "SOA" {
			continue
		}

		recConfig := &models.RecordConfig{
			Type:     recType,
			TTL:      uint32(*record.Ttl),
			Original: record,
		}
		recConfig.SetTarget(*record.Data)

		switch recType {
		case "SRV":
			var service, protocol string = "", "_tcp"

			if record.Weight != nil {
				recConfig.SrvWeight = uint16(*record.Weight)
			}
			if record.Port != nil {
				recConfig.SrvPort = uint16(*record.Port)
			}
			if record.Priority != nil {
				recConfig.SrvPriority = uint16(*record.Priority)
			}
			if record.Protocol != nil {
				protocol = *record.Protocol
			}
			if record.Service != nil {
				service = *record.Service
			}
			recConfig.SetLabel(fmt.Sprintf("%s.%s", service, strings.ToLower(protocol)), *domain.Name)
		case "TXT":
			recConfig.TxtStrings = append(recConfig.TxtStrings, *record.Data)
			fallthrough
		case "MX":
			if record.MxPriority != nil {
				recConfig.MxPreference = uint16(*record.MxPriority)
			}
			fallthrough
		default:
			recConfig.SetLabel(*record.Host, *domain.Name)
		}

		actual = append(actual, recConfig)
	}

	// Normalize
	models.PostProcessRecords(actual)

	return actual, nil
}

func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error {
	var ttl, preference, domainID int = int(desired.TTL), int(desired.MxPreference), *domain.Id
	var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort)
	var host, data, newType string = desired.GetLabel(), desired.GetTargetField(), desired.Type
	var err error

	srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`)

	return func() error {
		newRecord := datatypes.Dns_Domain_ResourceRecord{
			DomainId: &domainID,
			Ttl:      &ttl,
			Type:     &newType,
			Data:     &data,
			Host:     &host,
		}

		switch newType {
		case "MX":
			service := services.GetDnsDomainResourceRecordMxTypeService(s.Session)

			newRecord.MxPriority = &preference

			newMx := datatypes.Dns_Domain_ResourceRecord_MxType{
				Dns_Domain_ResourceRecord: newRecord,
			}

			_, err = service.CreateObject(&newMx)

		case "SRV":
			service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session)
			result := srvRegexp.FindStringSubmatch(host)

			if len(result) != 3 {
				return fmt.Errorf("SRV Record must match format \"_service._protocol\" not %s", host)
			}

			var serviceName, protocol string = result[1], strings.ToLower(result[2])

			newSrv := datatypes.Dns_Domain_ResourceRecord_SrvType{
				Dns_Domain_ResourceRecord: newRecord,
				Service:                   &serviceName,
				Port:                      &port,
				Priority:                  &priority,
				Protocol:                  &protocol,
				Weight:                    &weight,
			}

			_, err = service.CreateObject(&newSrv)

		default:
			service := services.GetDnsDomainResourceRecordService(s.Session)
			_, err = service.CreateObject(&newRecord)
		}

		return err
	}
}

func (s *SoftLayer) deleteRecordFunc(resID int) func() error {
	// seems to be no problem deleting MX and SRV records via common interface
	return func() error {
		_, err := services.GetDnsDomainResourceRecordService(s.Session).
			Id(resID).
			DeleteObject()

		return err
	}
}

func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceRecord, desired *models.RecordConfig) func() error {
	var ttl, preference int = int(desired.TTL), int(desired.MxPreference)
	var priority, weight, port int = int(desired.SrvPriority), int(desired.SrvWeight), int(desired.SrvPort)

	return func() error {
		var changes = false
		var err error

		switch desired.Type {
		case "MX":
			service := services.GetDnsDomainResourceRecordMxTypeService(s.Session)
			updated := datatypes.Dns_Domain_ResourceRecord_MxType{}

			label := desired.GetLabel()
			if label != *existing.Host {
				updated.Host = &label
				changes = true
			}

			target := desired.GetTargetField()
			if target != *existing.Data {
				updated.Data = &target
				changes = true
			}

			if ttl != *existing.Ttl {
				updated.Ttl = &ttl
				changes = true
			}

			if preference != *existing.MxPriority {
				updated.MxPriority = &preference
				changes = true
			}

			if !changes {
				return fmt.Errorf("didn't find changes when I expect some")
			}

			_, err = service.Id(*existing.Id).EditObject(&updated)

		case "SRV":
			service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session)
			updated := datatypes.Dns_Domain_ResourceRecord_SrvType{}

			label := desired.GetLabel()
			if label != *existing.Host {
				updated.Host = &label
				changes = true
			}

			target := desired.GetTargetField()
			if target != *existing.Data {
				updated.Data = &target
				changes = true
			}

			if ttl != *existing.Ttl {
				updated.Ttl = &ttl
				changes = true
			}

			if priority != *existing.Priority {
				updated.Priority = &priority
				changes = true
			}

			if weight != *existing.Weight {
				updated.Weight = &weight
				changes = true
			}

			if port != *existing.Port {
				updated.Port = &port
				changes = true
			}

			// TODO: handle service & protocol - or does that just result in a
			// delete and recreate?

			if !changes {
				return fmt.Errorf("didn't find changes when I expect some")
			}

			_, err = service.Id(*existing.Id).EditObject(&updated)

		default:
			service := services.GetDnsDomainResourceRecordService(s.Session)
			updated := datatypes.Dns_Domain_ResourceRecord{}

			label := desired.GetLabel()
			if label != *existing.Host {
				updated.Host = &label
				changes = true
			}

			target := desired.GetTargetField()
			if target != *existing.Data {
				updated.Data = &target
				changes = true
			}

			if ttl != *existing.Ttl {
				updated.Ttl = &ttl
				changes = true
			}

			if !changes {
				return fmt.Errorf("didn't find changes when I expect some")
			}

			_, err = service.Id(*existing.Id).EditObject(&updated)
		}

		return err
	}
}
<commit_msg>SOFTLAYER: Fixed Lets Encrypt Certificate issue #668 (#669)<commit_after>package softlayer

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/softlayer/softlayer-go/datatypes"
	"github.com/softlayer/softlayer-go/filter"
	"github.com/softlayer/softlayer-go/services"
	"github.com/softlayer/softlayer-go/session"

	"github.com/StackExchange/dnscontrol/v2/models"
	"github.com/StackExchange/dnscontrol/v2/providers"
	"github.com/StackExchange/dnscontrol/v2/providers/diff"
)

// SoftLayer is the protocol handle for this provider.
type SoftLayer struct {
	Session *session.Session
}

var features = providers.DocumentationNotes{
	providers.CanUseSRV:   providers.Can(),
	providers.CanGetZones: providers.Unimplemented(),
}

func init() {
	providers.RegisterDomainServiceProviderType("SOFTLAYER", newReg, features)
}

func newReg(conf map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
	s := session.New(conf["username"], conf["api_key"], conf["endpoint_url"], conf["timeout"])

	if len(s.UserName) == 0 || len(s.APIKey) == 0 {
		return nil, fmt.Errorf("SoftLayer UserName and APIKey must be provided")
	}

	// s.Debug = true

	api := &SoftLayer{
		Session: s,
	}

	return api, nil
}

// GetNameservers returns the nameservers for a domain.
func (s *SoftLayer) GetNameservers(domain string) ([]*models.Nameserver, error) {
	// Always use the same nameservers for softlayer
	nservers := []string{"ns1.softlayer.com", "ns2.softlayer.com"}
	return models.StringsToNameservers(nservers), nil
}

// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
func (client *SoftLayer) GetZoneRecords(domain string) (models.Records, error) {
	return nil, fmt.Errorf("not implemented")
	// This enables the get-zones subcommand.
	// Implement this by extracting the code from GetDomainCorrections into
	// a single function.  For most providers this should be relatively easy.
}

// GetDomainCorrections returns corrections to update a domain.
func (s *SoftLayer) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	corrections := []*models.Correction{}

	domain, err := s.getDomain(&dc.Name)

	if err != nil {
		return nil, err
	}

	actual, err := s.getExistingRecords(domain)

	if err != nil {
		return nil, err
	}

	_, create, delete, modify := diff.New(dc).IncrementalDiff(actual)

	for _, del := range delete {
		existing := del.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
		corrections = append(corrections, &models.Correction{
			Msg: del.String(),
			F:   s.deleteRecordFunc(*existing.Id),
		})
	}

	for _, cre := range create {
		corrections = append(corrections, &models.Correction{
			Msg: cre.String(),
			F:   s.createRecordFunc(cre.Desired, domain),
		})
	}

	for _, mod := range modify {
		existing := mod.Existing.Original.(datatypes.Dns_Domain_ResourceRecord)
		corrections = append(corrections, &models.Correction{
			Msg: mod.String(),
			F:   s.updateRecordFunc(&existing, mod.Desired),
		})
	}

	return corrections, nil
}

func (s *SoftLayer) getDomain(name *string) (*datatypes.Dns_Domain, error) {
	domains, err := services.GetAccountService(s.Session).
		Filter(filter.Path("domains.name").Eq(name).Build()).
		Mask("resourceRecords").
		GetDomains()

	if err != nil {
		return nil, err
	}

	if len(domains) == 0 {
		return nil, fmt.Errorf("Didn't find a domain matching %s", *name)
	} else if len(domains) > 1 {
		return nil, fmt.Errorf("Found %d domains matching %s", len(domains), *name)
	}

	return &domains[0], nil
}

func (s *SoftLayer) getExistingRecords(domain *datatypes.Dns_Domain) ([]*models.RecordConfig, error) {
	actual := []*models.RecordConfig{}

	for _, record := range domain.ResourceRecords {
		recType := strings.ToUpper(*record.Type)

		if recType == "SOA" {
			continue
		}

		recConfig := &models.RecordConfig{
			Type:     recType,
			TTL:      uint32(*record.Ttl),
			Original: record,
		}
		recConfig.SetTarget(*record.Data)

		switch recType {
		case "SRV":
			var service, protocol string = "", "_tcp"

			if record.Weight != nil {
				recConfig.SrvWeight = uint16(*record.Weight)
			}
			if record.Port != nil {
				recConfig.SrvPort = uint16(*record.Port)
			}
			if record.Priority != nil {
				recConfig.SrvPriority = uint16(*record.Priority)
			}
			if record.Protocol != nil {
				protocol = *record.Protocol
			}
			if record.Service != nil {
				service = *record.Service
			}
			recConfig.SetLabel(fmt.Sprintf("%s.%s", service, strings.ToLower(protocol)), *domain.Name)
		case "TXT":
			recConfig.TxtStrings = append(recConfig.TxtStrings, *record.Data)
			fallthrough
		case "MX":
			if record.MxPriority != nil {
				recConfig.MxPreference = uint16(*record.MxPriority)
			}
			fallthrough
		default:
			recConfig.SetLabel(*record.Host, *domain.Name)
		}

		actual = append(actual, recConfig)
	}

	// Normalize
	models.PostProcessRecords(actual)

	return actual, nil
}

func (s *SoftLayer) createRecordFunc(desired *models.RecordConfig, domain *datatypes.Dns_Domain) func() error {
	var ttl, preference, domainID int = verifyMinTTL(int(desired.TTL)), int(desired.MxPreference), *domain.Id
	var weight, priority, port int = int(desired.SrvWeight), int(desired.SrvPriority), int(desired.SrvPort)
	var host, data, newType string = desired.GetLabel(), desired.GetTargetField(), desired.Type
	var err error

	srvRegexp := regexp.MustCompile(`^_(?P<Service>\w+)\.\_(?P<Protocol>\w+)$`)

	return func() error {
		newRecord := datatypes.Dns_Domain_ResourceRecord{
			DomainId: &domainID,
			Ttl:      &ttl,
			Type:     &newType,
			Data:     &data,
			Host:     &host,
		}

		switch newType {
		case "MX":
			service := services.GetDnsDomainResourceRecordMxTypeService(s.Session)

			newRecord.MxPriority = &preference

			newMx := datatypes.Dns_Domain_ResourceRecord_MxType{
				Dns_Domain_ResourceRecord: newRecord,
			}

			_, err = service.CreateObject(&newMx)

		case "SRV":
			service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session)
			result := srvRegexp.FindStringSubmatch(host)

			if len(result) != 3 {
				return fmt.Errorf("SRV Record must match format \"_service._protocol\" not %s", host)
			}

			var serviceName, protocol string = result[1], strings.ToLower(result[2])

			newSrv := datatypes.Dns_Domain_ResourceRecord_SrvType{
				Dns_Domain_ResourceRecord: newRecord,
				Service:                   &serviceName,
				Port:                      &port,
				Priority:                  &priority,
				Protocol:                  &protocol,
				Weight:                    &weight,
			}

			_, err = service.CreateObject(&newSrv)

		default:
			service := services.GetDnsDomainResourceRecordService(s.Session)
			_, err = service.CreateObject(&newRecord)
		}

		return err
	}
}

func (s *SoftLayer) deleteRecordFunc(resID int) func() error {
	// seems to be no problem deleting MX and SRV records via common interface
	return func() error {
		_, err := services.GetDnsDomainResourceRecordService(s.Session).
			Id(resID).
			DeleteObject()

		return err
	}
}

func (s *SoftLayer) updateRecordFunc(existing *datatypes.Dns_Domain_ResourceRecord, desired *models.RecordConfig) func() error {
	var ttl, preference int = verifyMinTTL(int(desired.TTL)), int(desired.MxPreference)
	var priority, weight, port int = int(desired.SrvPriority), int(desired.SrvWeight), int(desired.SrvPort)

	return func() error {
		var changes = false
		var err error

		switch desired.Type {
		case "MX":
			service := services.GetDnsDomainResourceRecordMxTypeService(s.Session)
			updated := datatypes.Dns_Domain_ResourceRecord_MxType{}

			label := desired.GetLabel()
			if label != *existing.Host {
				updated.Host = &label
				changes = true
			}

			target := desired.GetTargetField()
			if target != *existing.Data {
				updated.Data = &target
				changes = true
			}

			if ttl != *existing.Ttl {
				updated.Ttl = &ttl
				changes = true
			}

			if preference != *existing.MxPriority {
				updated.MxPriority = &preference
				changes = true
			}

			if !changes {
				return fmt.Errorf("didn't find changes when I expect some")
			}

			_, err = service.Id(*existing.Id).EditObject(&updated)

		case "SRV":
			service := services.GetDnsDomainResourceRecordSrvTypeService(s.Session)
			updated := datatypes.Dns_Domain_ResourceRecord_SrvType{}

			label := desired.GetLabel()
			if label != *existing.Host {
				updated.Host = &label
				changes = true
			}

			target := desired.GetTargetField()
			if target != *existing.Data {
				updated.Data = &target
				changes = true
			}

			if ttl != *existing.Ttl {
				updated.Ttl = &ttl
				changes = true
			}

			if priority != *existing.Priority {
				updated.Priority = &priority
				changes = true
			}

			if weight != *existing.Weight {
				updated.Weight = &weight
				changes = true
			}

			if port != *existing.Port {
				updated.Port = &port
				changes = true
			}

			// TODO: handle service & protocol - or does that just result in a
			// delete and recreate?

			if !changes {
				return fmt.Errorf("didn't find changes when I expect some")
			}

			_, err = service.Id(*existing.Id).EditObject(&updated)

		default:
			service := services.GetDnsDomainResourceRecordService(s.Session)
			updated := datatypes.Dns_Domain_ResourceRecord{}

			label := desired.GetLabel()
			if label != *existing.Host {
				updated.Host = &label
				changes = true
			}

			target := desired.GetTargetField()
			if target != *existing.Data {
				updated.Data = &target
				changes = true
			}

			if ttl != *existing.Ttl {
				updated.Ttl = &ttl
				changes = true
			}

			if !changes {
				return fmt.Errorf("didn't find changes when I expect some")
			}

			_, err = service.Id(*existing.Id).EditObject(&updated)
		}

		return err
	}
}

func verifyMinTTL(ttl int) int {
	const minTTL = 60
	if ttl < minTTL {
		fmt.Printf("\nMODIFY TTL to Min supported TTL value: (ttl=%d) -> (ttl=%d)\n", ttl, minTTL)
		return minTTL
	}
	return ttl
}
<|endoftext|>


<commit_before>package mq

import (
	"fmt"

	"time"

	"strings"

	"github.com/Shopify/sarama"
	conf "github.com/nikugame/public/config"
)

type MsgContent struct {
	Content   string //内容
	MsgType   string //消息类型,充值、登录等等
	Key       string
	Timestamp time.Time
}

type Client struct {
	Addrs       string
	Producer    sarama.SyncProducer
	AsyncEnable bool
	config      *sarama.Config
	MsgContent  MsgContent
}

func (s *Client) init() {
	s.config = sarama.NewConfig()
	s.config.Producer.RequiredAcks = sarama.WaitForAll
	s.config.Producer.Partitioner = sarama.NewRandomPartitioner
	if len(s.Addrs) == 0 {
		//logs.Info("没有设置kafka的主机或端口，使用配置文件。")
		cf, _ := conf.NewConfig("ini", "conf/settings.conf")
		s.Addrs = cf.String("MqServer::addrs")
		//fmt.Println(s.Addrs)
		// s.connString = cf.String("MqServer::Host") + ":" + cf.String("MqServer::Port")
		//logs.Info("连接字符串为：%s", s.Addrs)
	}
}

//Send 新建一个对象
func (s *Client) Send() (int32, int64, error) {
	s.init()
	var err error
	s.Producer, err = sarama.NewSyncProducer(strings.Split(s.Addrs, ","), s.config)
	if err != nil {
		fmt.Println(err)
	}
	defer s.Producer.Close()
	return s.Producer.SendMessage(s.msg())
}

func (s *Client) msg() *sarama.ProducerMessage {
	var msg sarama.ProducerMessage
	if s.MsgContent.MsgType != "" {
		msg.Topic = s.MsgContent.MsgType
	} else {
		fmt.Println("Topic is empty!")
	}
	// msg.Partition = int32(-1)
	if !s.MsgContent.Timestamp.IsZero() {
		msg.Timestamp = s.MsgContent.Timestamp
	}

	if s.MsgContent.Key != "" {
		msg.Key = sarama.StringEncoder(s.MsgContent.Key)
	}

	if s.MsgContent.Content != "" {
		msg.Value = sarama.ByteEncoder(s.MsgContent.Content)

	}
	return &msg
}
<commit_msg>Revert "修改获取时间的方法"<commit_after>package mq

import (
	"fmt"

	"time"

	"strings"

	"github.com/Shopify/sarama"
	conf "github.com/nikugame/public/config"
)

type MsgContent struct {
	Content string //内容
	MsgType string //消息类型,充值、登录等等
	Key     string
}

type Client struct {
	Addrs       string
	Producer    sarama.SyncProducer
	AsyncEnable bool
	config      *sarama.Config
	MsgContent  MsgContent
}

func (s *Client) init() {
	s.config = sarama.NewConfig()
	s.config.Producer.RequiredAcks = sarama.WaitForAll
	s.config.Producer.Partitioner = sarama.NewRandomPartitioner
	if len(s.Addrs) == 0 {
		//logs.Info("没有设置kafka的主机或端口，使用配置文件。")
		cf, _ := conf.NewConfig("ini", "conf/settings.conf")
		s.Addrs = cf.String("MqServer::addrs")
		//fmt.Println(s.Addrs)
		// s.connString = cf.String("MqServer::Host") + ":" + cf.String("MqServer::Port")
		//logs.Info("连接字符串为：%s", s.Addrs)
	}
}

//Send 新建一个对象
func (s *Client) Send() (int32, int64, error) {
	s.init()
	var err error
	s.Producer, err = sarama.NewSyncProducer(strings.Split(s.Addrs, ","), s.config)
	if err != nil {
		fmt.Println(err)
	}
	defer s.Producer.Close()
	return s.Producer.SendMessage(s.msg())
}

func (s *Client) msg() *sarama.ProducerMessage {
	var msg sarama.ProducerMessage
	if s.MsgContent.MsgType != "" {
		msg.Topic = s.MsgContent.MsgType
	} else {
		fmt.Println("Topic is empty!")
	}
	// msg.Partition = int32(-1)
	msg.Timestamp = time.Now().Local()
	if s.MsgContent.Key != "" {
		msg.Key = sarama.StringEncoder(s.MsgContent.Key)
	}

	if s.MsgContent.Content != "" {
		msg.Value = sarama.ByteEncoder(s.MsgContent.Content)

	}
	return &msg
}
<|endoftext|>


<commit_before>package multipass

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"html"
	"log"
	"net"
	"net/http"
	"net/smtp"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/mholt/caddy/caddyhttp/httpserver"

	jose "gopkg.in/square/go-jose.v1"
)

var ErrInvalidToken = errors.New("invalid token")

type Auth struct {
	*Multipass
	Next httpserver.Handler
}

type Rule struct {
	Basepath  string
	Expires   time.Duration
	Resources []string
	Handles   []string

	SMTPAddr, SMTPUser, SMTPPass string
	MailFrom, MailTmpl           string
}

type Multipass struct {
	Resources []string
	Basepath  string
	SiteAddr  string
	Expires   time.Duration

	Handler HandleService
	signer  jose.Signer
	key     *rsa.PrivateKey
}

func NewMultipassFromRule(r Rule) (*Multipass, error) {
	m, err := NewMultipass()
	if err != nil {
		return nil, err
	}
	if len(r.Resources) > 0 {
		m.Resources = r.Resources
	}
	if len(r.Basepath) > 0 {
		m.Basepath = r.Basepath
	}
	if r.Expires > 0 {
		m.Expires = r.Expires
	}

	mailTmpl := emailTemplate
	if len(r.MailTmpl) > 0 {
		mailTmpl = r.MailTmpl
	}

	host := "localhost"
	port := "25"
	if h, p, err := net.SplitHostPort(r.SMTPAddr); err == nil {
		host = h
		port = p
	} else {
		host = r.SMTPAddr
	}

	var auth smtp.Auth
	if len(r.SMTPUser) > 0 && len(r.SMTPPass) > 0 {
		auth = smtp.PlainAuth("", r.SMTPUser, r.SMTPPass, host)
	}

	addr := strings.Join([]string{host, port}, ":")
	from := html.UnescapeString(r.MailFrom)
	handler := NewEmailHandler(addr, auth, from, mailTmpl)
	for _, handle := range r.Handles {
		handler.Register(handle)
	}
	m.Handler = handler

	return m, nil
}

func NewMultipass() (*Multipass, error) {
	pk, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}
	signer, err := jose.NewSigner(jose.PS512, pk)
	if err != nil {
		return nil, err
	}
	return &Multipass{
		Resources: []string{"/"},
		Basepath:  "/",
		Expires:   time.Hour * 24,
		key:       pk,
		signer:    signer,
	}, nil
}

// Claims are part of the JSON web token
type Claims struct {
	Handle    string   `json:"handle"`
	Resources []string `json:"resources"`
	Expires   int64    `json:"exp"`
}

func (m *Multipass) AccessToken(handle string) (tokenStr string, err error) {
	exp := time.Now().Add(m.Expires)
	claims := &Claims{
		Handle:    handle,
		Resources: m.Resources,
		Expires:   exp.Unix(),
	}
	payload, err := json.Marshal(claims)
	if err != nil {
		return "", err
	}
	jws, err := m.signer.Sign(payload)
	if err != nil {
		return "", err
	}

	return jws.CompactSerialize()
}

func NewLoginURL(siteaddr, basepath, token string, v url.Values) (*url.URL, error) {
	u, err := url.Parse(siteaddr)
	if err != nil {
		return u, err
	}
	u.Path = path.Join(basepath, "login")
	v.Set("token", token)
	u.RawQuery = v.Encode()
	return u, nil
}

func loginHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	if r.Method == "POST" {
		r.ParseForm()
		handle := r.PostForm.Get("handle")
		if len(handle) == 0 {
			loc := path.Join(m.Basepath, "login")
			http.Redirect(w, r, loc, http.StatusSeeOther)
			return http.StatusSeeOther, nil
		}
		if m.Handler.Listed(handle) {
			token, err := m.AccessToken(handle)
			if err != nil {
				log.Print(err)
			}
			values := url.Values{}
			if s := r.PostForm.Get("url"); len(s) > 0 {
				values.Set("url", s)
			}
			loginURL, err := NewLoginURL(m.SiteAddr, m.Basepath, token, values)
			if err != nil {
				log.Print(err)
			}
			if err := m.Handler.Notify(handle, loginURL.String()); err != nil {
				log.Print(err)
			}
		}
		w.Header().Add("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte("A login link has been sent to user with handle " + handle + " if your handle is authorized"))
		return http.StatusOK, nil
	}
	if r.Method == "GET" {
		if tokenStr := r.URL.Query().Get("token"); len(tokenStr) > 0 {
			cookie := &http.Cookie{
				Name:  "jwt_token",
				Value: tokenStr,
				Path:  "/",
			}
			http.SetCookie(w, cookie)

			nexturl := m.SiteAddr
			if s := r.URL.Query().Get("url"); len(s) > 0 {
				nexturl = s
			}
			http.Redirect(w, r, nexturl, http.StatusSeeOther)
			return http.StatusSeeOther, nil
		}
		w.Header().Add("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte("<form action=" + r.URL.Path + " method=POST><input type=text name=handle /><input type=submit></form>"))
		return http.StatusOK, nil
	}
	return http.StatusMethodNotAllowed, nil
}

func loginformHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	nextURL, err := url.Parse(m.SiteAddr)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	nextURL.Path = r.URL.Path
	nextURL.RawQuery = r.URL.RawQuery
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Write([]byte(`

Multipass

Enter your user handle to gain access to ` + nextURL.String() + `

<form action="` + path.Join(m.Basepath, "/login") + `" method=POST>
<input type=hidden name=url value="` + nextURL.String() + `"/>
<input type=text name=handle />
<input type=submit>
</form>
`))
	return http.StatusOK, nil
}

func signoutHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	if cookie, err := r.Cookie("jwt_token"); err == nil {
		cookie.Expires = time.Now().AddDate(-1, 0, 0)
		cookie.MaxAge = -1
		cookie.Path = "/"
		http.SetCookie(w, cookie)
	}
	loc := path.Join(m.Basepath, "login")
	http.Redirect(w, r, loc, http.StatusSeeOther)
	return http.StatusSeeOther, nil
}

func publickeyHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	data, err := x509.MarshalPKIXPublicKey(&m.key.PublicKey)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	block := &pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: data,
	}
	w.Header().Set("Content-Type", "application/pkix-cert")
	if err := pem.Encode(w, block); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func tokenHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	// Extract token from HTTP header, query parameter or cookie
	tokenStr, err := extractToken(r)
	if err != nil {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	var claims *Claims
	if claims, err = validateToken(tokenStr, m.key.PublicKey); err != nil {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	// Authorize handle claim
	if ok := m.Handler.Listed(claims.Handle); !ok {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	// Verify path claim
	var match bool
	for _, path := range claims.Resources {
		if httpserver.Path(r.URL.Path).Matches(path) {
			match = true
			continue
		}
	}
	if !match {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	return http.StatusOK, nil
}

func (a *Auth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	m := a.Multipass

	switch r.URL.Path {
	case path.Join(m.Basepath, "pub.cer"):
		return publickeyHandler(w, r, m)
	case path.Join(m.Basepath, "login"):
		return loginHandler(w, r, m)
	case path.Join(m.Basepath, "signout"):
		return signoutHandler(w, r, m)
	}

	for _, path := range m.Resources {
		if httpserver.Path(r.URL.Path).Matches(path) {
			if code, err := tokenHandler(w, r, m); err != nil {
				w.WriteHeader(code)
				return loginformHandler(w, r, m)
			}
		}
	}

	return a.Next.ServeHTTP(w, r)
}

// extractToken returns the JWT token embedded in the given request.
// JWT tokens can be embedded in the header prefixed with "Bearer ", with a
// "token" key query parameter or a cookie named "jwt_token".
func extractToken(r *http.Request) (string, error) {
	//from header
	if h := r.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
		if len(h) > 7 {
			return h[7:], nil
		}
	}

	//from query parameter
	if token := r.URL.Query().Get("token"); len(token) > 0 {
		return token, nil
	}

	//from cookie
	if cookie, err := r.Cookie("jwt_token"); err == nil {
		return cookie.Value, nil
	}

	return "", fmt.Errorf("no token found")
}

func validateToken(token string, key rsa.PublicKey) (*Claims, error) {
	claims := &Claims{}

	// Verify token signature
	payload, err := verifyToken(token, key)
	if err != nil {
		return nil, err
	}
	// Unmarshal token claims
	if err := json.Unmarshal(payload, claims); err != nil {
		return nil, err
	}
	// Verify expire claim
	if time.Unix(claims.Expires, 0).Before(time.Now()) {
		return nil, errors.New("Token expired")
	}
	return claims, nil
}

func verifyToken(token string, key rsa.PublicKey) ([]byte, error) {
	var data []byte

	obj, err := jose.ParseSigned(token)
	if err != nil {
		return data, err
	}
	data, err = obj.Verify(&key)
	if err != nil {
		return data, err
	}
	return data, nil
}
<commit_msg>Make sure basepath is absolute<commit_after>package multipass

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"html"
	"log"
	"net"
	"net/http"
	"net/smtp"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/mholt/caddy/caddyhttp/httpserver"

	jose "gopkg.in/square/go-jose.v1"
)

var ErrInvalidToken = errors.New("invalid token")

type Auth struct {
	*Multipass
	Next httpserver.Handler
}

type Rule struct {
	Basepath  string
	Expires   time.Duration
	Resources []string
	Handles   []string

	SMTPAddr, SMTPUser, SMTPPass string
	MailFrom, MailTmpl           string
}

type Multipass struct {
	Resources []string
	Basepath  string
	SiteAddr  string
	Expires   time.Duration

	Handler HandleService
	signer  jose.Signer
	key     *rsa.PrivateKey
}

func NewMultipassFromRule(r Rule) (*Multipass, error) {
	m, err := NewMultipass()
	if err != nil {
		return nil, err
	}
	if len(r.Resources) > 0 {
		m.Resources = r.Resources
	}
	if len(r.Basepath) > 0 {
		m.Basepath = path.Join("/", r.Basepath)
	}
	if r.Expires > 0 {
		m.Expires = r.Expires
	}

	mailTmpl := emailTemplate
	if len(r.MailTmpl) > 0 {
		mailTmpl = r.MailTmpl
	}

	host := "localhost"
	port := "25"
	if h, p, err := net.SplitHostPort(r.SMTPAddr); err == nil {
		host = h
		port = p
	} else {
		host = r.SMTPAddr
	}

	var auth smtp.Auth
	if len(r.SMTPUser) > 0 && len(r.SMTPPass) > 0 {
		auth = smtp.PlainAuth("", r.SMTPUser, r.SMTPPass, host)
	}

	addr := strings.Join([]string{host, port}, ":")
	from := html.UnescapeString(r.MailFrom)
	handler := NewEmailHandler(addr, auth, from, mailTmpl)
	for _, handle := range r.Handles {
		handler.Register(handle)
	}
	m.Handler = handler

	return m, nil
}

func NewMultipass() (*Multipass, error) {
	pk, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}
	signer, err := jose.NewSigner(jose.PS512, pk)
	if err != nil {
		return nil, err
	}
	return &Multipass{
		Resources: []string{"/"},
		Basepath:  "/",
		Expires:   time.Hour * 24,
		key:       pk,
		signer:    signer,
	}, nil
}

// Claims are part of the JSON web token
type Claims struct {
	Handle    string   `json:"handle"`
	Resources []string `json:"resources"`
	Expires   int64    `json:"exp"`
}

func (m *Multipass) AccessToken(handle string) (tokenStr string, err error) {
	exp := time.Now().Add(m.Expires)
	claims := &Claims{
		Handle:    handle,
		Resources: m.Resources,
		Expires:   exp.Unix(),
	}
	payload, err := json.Marshal(claims)
	if err != nil {
		return "", err
	}
	jws, err := m.signer.Sign(payload)
	if err != nil {
		return "", err
	}

	return jws.CompactSerialize()
}

func NewLoginURL(siteaddr, basepath, token string, v url.Values) (*url.URL, error) {
	u, err := url.Parse(siteaddr)
	if err != nil {
		return u, err
	}
	u.Path = path.Join(basepath, "login")
	v.Set("token", token)
	u.RawQuery = v.Encode()
	return u, nil
}

func loginHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	if r.Method == "POST" {
		r.ParseForm()
		handle := r.PostForm.Get("handle")
		if len(handle) == 0 {
			loc := path.Join(m.Basepath, "login")
			http.Redirect(w, r, loc, http.StatusSeeOther)
			return http.StatusSeeOther, nil
		}
		if m.Handler.Listed(handle) {
			token, err := m.AccessToken(handle)
			if err != nil {
				log.Print(err)
			}
			values := url.Values{}
			if s := r.PostForm.Get("url"); len(s) > 0 {
				values.Set("url", s)
			}
			loginURL, err := NewLoginURL(m.SiteAddr, m.Basepath, token, values)
			if err != nil {
				log.Print(err)
			}
			if err := m.Handler.Notify(handle, loginURL.String()); err != nil {
				log.Print(err)
			}
		}
		location := path.Join(m.Basepath, "/login/confirm")
		http.Redirect(w, r, location, http.StatusSeeOther)
		return http.StatusSeeOther, nil
	}
	if r.Method == "GET" {
		if tokenStr := r.URL.Query().Get("token"); len(tokenStr) > 0 {
			cookie := &http.Cookie{
				Name:  "jwt_token",
				Value: tokenStr,
				Path:  "/",
			}
			http.SetCookie(w, cookie)

			nexturl := m.SiteAddr
			if s := r.URL.Query().Get("url"); len(s) > 0 {
				nexturl = s
			}
			http.Redirect(w, r, nexturl, http.StatusSeeOther)
			return http.StatusSeeOther, nil
		}
		nextURL, err := url.Parse(m.SiteAddr)
		if err != nil {
			return http.StatusInternalServerError, err
		}
		nextURL.Path = r.URL.Path
		nextURL.RawQuery = r.URL.RawQuery
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		w.Write([]byte(`

Multipass

This resource is protected by Multipass. Enter your user handle to gain access.

<form action="` + path.Join(m.Basepath, "/login") + `" method=POST>
<input type=hidden name=url value="` + nextURL.String() + `"/>
<input type=text name=handle />
<input type=submit>
</form>
`))
		return http.StatusOK, nil
	}
	return http.StatusMethodNotAllowed, nil
}

func confirmHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	w.Header().Add("Content-Type", "text/html; charset=utf-8")
	w.Write([]byte("A login link has been sent to you"))
	return http.StatusOK, nil
}

func signoutHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	if cookie, err := r.Cookie("jwt_token"); err == nil {
		cookie.Expires = time.Now().AddDate(-1, 0, 0)
		cookie.MaxAge = -1
		cookie.Path = "/"
		http.SetCookie(w, cookie)
	}
	loc := path.Join(m.Basepath, "login")
	http.Redirect(w, r, loc, http.StatusSeeOther)
	return http.StatusSeeOther, nil
}

func publickeyHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	data, err := x509.MarshalPKIXPublicKey(&m.key.PublicKey)
	if err != nil {
		return http.StatusInternalServerError, err
	}
	block := &pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: data,
	}
	w.Header().Set("Content-Type", "application/pkix-cert")
	if err := pem.Encode(w, block); err != nil {
		return http.StatusInternalServerError, err
	}
	return http.StatusOK, nil
}

func tokenHandler(w http.ResponseWriter, r *http.Request, m *Multipass) (int, error) {
	// Extract token from HTTP header, query parameter or cookie
	tokenStr, err := extractToken(r)
	if err != nil {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	var claims *Claims
	if claims, err = validateToken(tokenStr, m.key.PublicKey); err != nil {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	// Authorize handle claim
	if ok := m.Handler.Listed(claims.Handle); !ok {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	// Verify path claim
	var match bool
	for _, path := range claims.Resources {
		if httpserver.Path(r.URL.Path).Matches(path) {
			match = true
			continue
		}
	}
	if !match {
		return http.StatusUnauthorized, ErrInvalidToken
	}
	return http.StatusOK, nil
}

func (a *Auth) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	m := a.Multipass

	switch r.URL.Path {
	case path.Join(m.Basepath, "pub.cer"):
		return publickeyHandler(w, r, m)
	case path.Join(m.Basepath, "login"):
		return loginHandler(w, r, m)
	case path.Join(m.Basepath, "login/confirm"):
		return confirmHandler(w, r, m)
	case path.Join(m.Basepath, "signout"):
		return signoutHandler(w, r, m)
	}

	for _, path := range m.Resources {
		if httpserver.Path(r.URL.Path).Matches(path) {
			if code, err := tokenHandler(w, r, m); err != nil {
				w.WriteHeader(code)
				return loginHandler(w, r, m)
			}
		}
	}

	return a.Next.ServeHTTP(w, r)
}

// extractToken returns the JWT token embedded in the given request.
// JWT tokens can be embedded in the header prefixed with "Bearer ", with a
// "token" key query parameter or a cookie named "jwt_token".
func extractToken(r *http.Request) (string, error) {
	//from header
	if h := r.Header.Get("Authorization"); strings.HasPrefix(h, "Bearer ") {
		if len(h) > 7 {
			return h[7:], nil
		}
	}

	//from query parameter
	if token := r.URL.Query().Get("token"); len(token) > 0 {
		return token, nil
	}

	//from cookie
	if cookie, err := r.Cookie("jwt_token"); err == nil {
		return cookie.Value, nil
	}

	return "", fmt.Errorf("no token found")
}

func validateToken(token string, key rsa.PublicKey) (*Claims, error) {
	claims := &Claims{}

	// Verify token signature
	payload, err := verifyToken(token, key)
	if err != nil {
		return nil, err
	}
	// Unmarshal token claims
	if err := json.Unmarshal(payload, claims); err != nil {
		return nil, err
	}
	// Verify expire claim
	if time.Unix(claims.Expires, 0).Before(time.Now()) {
		return nil, errors.New("Token expired")
	}
	return claims, nil
}

func verifyToken(token string, key rsa.PublicKey) ([]byte, error) {
	var data []byte

	obj, err := jose.ParseSigned(token)
	if err != nil {
		return data, err
	}
	data, err = obj.Verify(&key)
	if err != nil {
		return data, err
	}
	return data, nil
}
<|endoftext|>