<commit_before>package overseer

import (
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/kardianos/osext"
)

var tmpBinPath = filepath.Join(os.TempDir(), "overseer-"+token())

//a overseer master process
type master struct {
	*Config
	slaveID             int
	slaveCmd            *exec.Cmd
	slaveExtraFiles     []*os.File
	binPath, tmpBinPath string
	binPerms            os.FileMode
	binHash             []byte
	restartMux          sync.Mutex
	restarting          bool
	restartedAt         time.Time
	restarted           chan bool
	awaitingUSR1        bool
	descriptorsReleased chan bool
	signalledAt         time.Time
	printCheckUpdate    bool
}

func (mp *master) run() error {
	mp.debugf("run")
	if err := mp.checkBinary(); err != nil {
		return err
	}
	if mp.Config.Fetcher != nil {
		if err := mp.Config.Fetcher.Init(); err != nil {
			mp.warnf("fetcher init failed (%s). fetcher disabled.", err)
			mp.Config.Fetcher = nil
		}
	}
	mp.setupSignalling()
	if err := mp.retreiveFileDescriptors(); err != nil {
		return err
	}
	if mp.Config.Fetcher != nil {
		mp.printCheckUpdate = true
		mp.fetch()
		go mp.fetchLoop()
	}
	return mp.forkLoop()
}

func (mp *master) checkBinary() error {
	//get path to binary and confirm its writable
	binPath, err := osext.Executable()
	if err != nil {
		return fmt.Errorf("failed to find binary path (%s)", err)
	}
	mp.binPath = binPath
	if info, err := os.Stat(binPath); err != nil {
		return fmt.Errorf("failed to stat binary (%s)", err)
	} else if info.Size() == 0 {
		return fmt.Errorf("binary file is empty")
	} else {
		//copy permissions
		mp.binPerms = info.Mode()
	}
	f, err := os.Open(binPath)
	if err != nil {
		return fmt.Errorf("cannot read binary (%s)", err)
	}
	//initial hash of file
	hash := sha1.New()
	io.Copy(hash, f)
	mp.binHash = hash.Sum(nil)
	f.Close()
	//test bin<->tmpbin moves
	if err := move(tmpBinPath, mp.binPath); err != nil {
		return fmt.Errorf("cannot move binary (%s)", err)
	}
	if err := move(mp.binPath, tmpBinPath); err != nil {
		return fmt.Errorf("cannot move binary back (%s)", err)
	}
	return nil
}

func (mp *master) setupSignalling() {
	//updater-forker comms
	mp.restarted = make(chan bool)
	mp.descriptorsReleased = make(chan bool)
	//read all master process signals
	signals := make(chan os.Signal)
	signal.Notify(signals)
	go func() {
		for s := range signals {
			mp.handleSignal(s)
		}
	}()
}

func (mp *master) handleSignal(s os.Signal) {
	if s == mp.RestartSignal {
		//user initiated manual restart
		mp.triggerRestart()
	} else if s.String() == "child exited" {
		// will occur on every restart, ignore it
	} else
	//**during a restart** a SIGUSR1 signals
	//to the master process that, the file
	//descriptors have been released
	if mp.awaitingUSR1 && s == SIGUSR1 {
		mp.debugf("signaled, sockets ready")
		mp.awaitingUSR1 = false
		mp.descriptorsReleased <- true
	} else
	//while the slave process is running, proxy
	//all signals through
	if mp.slaveCmd != nil && mp.slaveCmd.Process != nil {
		mp.debugf("proxy signal (%s)", s)
		mp.sendSignal(s)
	} else
	//otherwise if not running, kill on CTRL+c
	if s == os.Interrupt {
		mp.debugf("interupt with no slave")
		os.Exit(1)
	} else {
		mp.debugf("signal discarded (%s), no slave process", s)
	}
}

func (mp *master) sendSignal(s os.Signal) {
	if err := mp.slaveCmd.Process.Signal(s); err != nil {
		mp.debugf("signal failed (%s), assuming slave process died unexpectedly", err)
		os.Exit(1)
	}
}

func (mp *master) retreiveFileDescriptors() error {
	mp.slaveExtraFiles = make([]*os.File, len(mp.Config.Addresses))
	for i, addr := range mp.Config.Addresses {
		a, err := net.ResolveTCPAddr("tcp", addr)
		if err != nil {
			return fmt.Errorf("Invalid address %s (%s)", addr, err)
		}
		l, err := net.ListenTCP("tcp", a)
		if err != nil {
			return err
		}
		f, err := l.File()
		if err != nil {
			return fmt.Errorf("Failed to retreive fd for: %s (%s)", addr, err)
		}
		if err := l.Close(); err != nil {
			return fmt.Errorf("Failed to close listener for: %s (%s)", addr, err)
		}
		mp.slaveExtraFiles[i] = f
	}
	return nil
}

//fetchLoop is run in a goroutine
func (mp *master) fetchLoop() {
	min := mp.Config.MinFetchInterval
	time.Sleep(min)
	for {
		t0 := time.Now()
		mp.fetch()
		//duration fetch of fetch
		diff := time.Now().Sub(t0)
		if diff < min {
			delay := min - diff
			//ensures at least MinFetchInterval delay.
			//should be throttled by the fetcher!
			time.Sleep(delay)
		}
	}
}

func (mp *master) fetch() {
	if mp.restarting {
		return //skip if restarting
	}
	if mp.printCheckUpdate {
		mp.debugf("checking for updates...")
	}
	reader, err := mp.Fetcher.Fetch()
	if err != nil {
		mp.debugf("failed to get latest version: %s", err)
		return
	}
	if reader == nil {
		if mp.printCheckUpdate {
			mp.debugf("no updates")
		}
		mp.printCheckUpdate = false
		return //fetcher has explicitly said there are no updates
	}
	mp.printCheckUpdate = true
	mp.debugf("streaming update...")
	//optional closer
	if closer, ok := reader.(io.Closer); ok {
		defer closer.Close()
	}
	tmpBin, err := os.OpenFile(tmpBinPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		mp.warnf("failed to open temp binary: %s", err)
		return
	}
	defer func() {
		tmpBin.Close()
		os.Remove(tmpBinPath)
	}()
	//tee off to sha1
	hash := sha1.New()
	reader = io.TeeReader(reader, hash)
	//write to a temp file
	_, err = io.Copy(tmpBin, reader)
	if err != nil {
		mp.warnf("failed to write temp binary: %s", err)
		return
	}
	//compare hash
	newHash := hash.Sum(nil)
	if bytes.Equal(mp.binHash, newHash) {
		mp.debugf("hash match - skip")
		return
	}
	//copy permissions
	if err := chmod(tmpBin, mp.binPerms); err != nil {
		mp.warnf("failed to make temp binary executable: %s", err)
		return
	}
	if err := chown(tmpBin, uid, gid); err != nil {
		mp.warnf("failed to change owner of binary: %s", err)
		return
	}
	if _, err := tmpBin.Stat(); err != nil {
		mp.warnf("failed to stat temp binary: %s", err)
		return
	}
	tmpBin.Close()
	if _, err := os.Stat(tmpBinPath); err != nil {
		mp.warnf("failed to stat temp binary by path: %s", err)
		return
	}
	if mp.Config.PreUpgrade != nil {
		if err := mp.Config.PreUpgrade(tmpBinPath); err != nil {
			mp.warnf("user cancelled upgrade: %s", err)
			return
		}
	}
	//overseer sanity check, dont replace our good binary with a non-executable file
	tokenIn := token()
	cmd := exec.Command(tmpBinPath)
	cmd.Env = []string{envBinCheck + "=" + tokenIn}
	returned := false
	go func() {
		time.Sleep(5 * time.Second)
		if !returned {
			mp.warnf("sanity check against fetched executable timed-out, check overseer is running")
			if cmd.Process != nil {
				cmd.Process.Kill()
			}
		}
	}()
	tokenOut, err := cmd.CombinedOutput()
	returned = true
	if err != nil {
		mp.warnf("failed to run temp binary: %s (%s) output \"%s\"", err, tmpBinPath, tokenOut)
		return
	}
	if tokenIn != string(tokenOut) {
		mp.warnf("sanity check failed")
		return
	}
	//overwrite!
	if err := move(mp.binPath, tmpBinPath); err != nil {
		mp.warnf("failed to overwrite binary: %s", err)
		return
	}
	mp.debugf("upgraded binary (%x -> %x)", mp.binHash[:12], newHash[:12])
	mp.binHash = newHash
	//binary successfully replaced
	if !mp.Config.NoRestartAfterFetch {
		mp.triggerRestart()
	}
	//and keep fetching...
	return
}

func (mp *master) triggerRestart() {
	if mp.restarting {
		mp.debugf("already graceful restarting")
		return //skip
	} else if mp.slaveCmd == nil || mp.restarting {
		mp.debugf("no slave process")
		return //skip
	}
	mp.debugf("graceful restart triggered")
	mp.restarting = true
	mp.awaitingUSR1 = true
	mp.signalledAt = time.Now()
	mp.sendSignal(mp.Config.RestartSignal) //ask nicely to terminate
	select {
	case <-mp.restarted:
		//success
		mp.debugf("restart success")
	case <-time.After(mp.TerminateTimeout):
		//times up mr. process, we did ask nicely!
		mp.debugf("graceful timeout, forcing exit")
		mp.sendSignal(os.Kill)
	}
}

//not a real fork
func (mp *master) forkLoop() error {
	//loop, restart command
	for {
		if err := mp.fork(); err != nil {
			return err
		}
	}
}

func (mp *master) fork() error {
	mp.debugf("starting %s", mp.binPath)
	cmd := exec.Command(mp.binPath)
	//mark this new process as the "active" slave process.
	//this process is assumed to be holding the socket files.
	mp.slaveCmd = cmd
	mp.slaveID++
	//provide the slave process with some state
	e := os.Environ()
	e = append(e, envBinID+"="+hex.EncodeToString(mp.binHash))
	e = append(e, envBinPath+"="+mp.binPath)
	e = append(e, envSlaveID+"="+strconv.Itoa(mp.slaveID))
	e = append(e, envIsSlave+"=1")
	e = append(e, envNumFDs+"="+strconv.Itoa(len(mp.slaveExtraFiles)))
	cmd.Env = e
	//inherit master args/stdfiles
	cmd.Args = os.Args
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	//include socket files
	cmd.ExtraFiles = mp.slaveExtraFiles
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("Failed to start slave process: %s", err)
	}
	//was scheduled to restart, notify success
	if mp.restarting {
		mp.restartedAt = time.Now()
		mp.restarting = false
		mp.restarted <- true
	}
	//convert wait into channel
	cmdwait := make(chan error)
	go func() {
		cmdwait <- cmd.Wait()
	}()
	//wait....
	select {
	case err := <-cmdwait:
		//program exited before releasing descriptors
		//proxy exit code out to master
		code := 0
		if err != nil {
			code = 1
			if exiterr, ok := err.(*exec.ExitError); ok {
				if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
					code = status.ExitStatus()
				}
			}
		}
		mp.debugf("prog exited with %d", code)
		//if a restarts are disabled or if it was an
		//unexpected creash, proxy this exit straight
		//through to the main process
		if mp.NoRestart || !mp.restarting {
			os.Exit(code)
		}
	case <-mp.descriptorsReleased:
		//if descriptors are released, the program
		//has yielded control of its sockets and
		//a parallel instance of the program can be
		//started safely. it should serve state.Listeners
		//to ensure downtime is kept at <1sec. The previous
		//cmd.Wait() will still be consumed though the
		//result will be discarded.
	}
	return nil
}

func (mp *master) debugf(f string, args ...interface{}) {
	if mp.Config.Debug {
		log.Printf("[overseer master] "+f, args...)
	}
}

func (mp *master) warnf(f string, args ...interface{}) {
	if mp.Config.Debug || !mp.Config.NoWarn {
		log.Printf("[overseer master] "+f, args...)
	}
}

func token() string {
	buff := make([]byte, 8)
	rand.Read(buff)
	return hex.EncodeToString(buff)
}
<commit_msg>fix rare nil ptr panic<commit_after>package overseer

import (
	"bytes"
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/kardianos/osext"
)

var tmpBinPath = filepath.Join(os.TempDir(), "overseer-"+token())

//a overseer master process
type master struct {
	*Config
	slaveID             int
	slaveCmd            *exec.Cmd
	slaveExtraFiles     []*os.File
	binPath, tmpBinPath string
	binPerms            os.FileMode
	binHash             []byte
	restartMux          sync.Mutex
	restarting          bool
	restartedAt         time.Time
	restarted           chan bool
	awaitingUSR1        bool
	descriptorsReleased chan bool
	signalledAt         time.Time
	printCheckUpdate    bool
}

func (mp *master) run() error {
	mp.debugf("run")
	if err := mp.checkBinary(); err != nil {
		return err
	}
	if mp.Config.Fetcher != nil {
		if err := mp.Config.Fetcher.Init(); err != nil {
			mp.warnf("fetcher init failed (%s). fetcher disabled.", err)
			mp.Config.Fetcher = nil
		}
	}
	mp.setupSignalling()
	if err := mp.retreiveFileDescriptors(); err != nil {
		return err
	}
	if mp.Config.Fetcher != nil {
		mp.printCheckUpdate = true
		mp.fetch()
		go mp.fetchLoop()
	}
	return mp.forkLoop()
}

func (mp *master) checkBinary() error {
	//get path to binary and confirm its writable
	binPath, err := osext.Executable()
	if err != nil {
		return fmt.Errorf("failed to find binary path (%s)", err)
	}
	mp.binPath = binPath
	if info, err := os.Stat(binPath); err != nil {
		return fmt.Errorf("failed to stat binary (%s)", err)
	} else if info.Size() == 0 {
		return fmt.Errorf("binary file is empty")
	} else {
		//copy permissions
		mp.binPerms = info.Mode()
	}
	f, err := os.Open(binPath)
	if err != nil {
		return fmt.Errorf("cannot read binary (%s)", err)
	}
	//initial hash of file
	hash := sha1.New()
	io.Copy(hash, f)
	mp.binHash = hash.Sum(nil)
	f.Close()
	//test bin<->tmpbin moves
	if err := move(tmpBinPath, mp.binPath); err != nil {
		return fmt.Errorf("cannot move binary (%s)", err)
	}
	if err := move(mp.binPath, tmpBinPath); err != nil {
		return fmt.Errorf("cannot move binary back (%s)", err)
	}
	return nil
}

func (mp *master) setupSignalling() {
	//updater-forker comms
	mp.restarted = make(chan bool)
	mp.descriptorsReleased = make(chan bool)
	//read all master process signals
	signals := make(chan os.Signal)
	signal.Notify(signals)
	go func() {
		for s := range signals {
			mp.handleSignal(s)
		}
	}()
}

func (mp *master) handleSignal(s os.Signal) {
	if s == mp.RestartSignal {
		//user initiated manual restart
		mp.triggerRestart()
	} else if s.String() == "child exited" {
		// will occur on every restart, ignore it
	} else
	//**during a restart** a SIGUSR1 signals
	//to the master process that, the file
	//descriptors have been released
	if mp.awaitingUSR1 && s == SIGUSR1 {
		mp.debugf("signaled, sockets ready")
		mp.awaitingUSR1 = false
		mp.descriptorsReleased <- true
	} else
	//while the slave process is running, proxy
	//all signals through
	if mp.slaveCmd != nil && mp.slaveCmd.Process != nil {
		mp.debugf("proxy signal (%s)", s)
		mp.sendSignal(s)
	} else
	//otherwise if not running, kill on CTRL+c
	if s == os.Interrupt {
		mp.debugf("interupt with no slave")
		os.Exit(1)
	} else {
		mp.debugf("signal discarded (%s), no slave process", s)
	}
}

func (mp *master) sendSignal(s os.Signal) {
	if mp.slaveCmd != nil && mp.slaveCmd.Process != nil {
		if err := mp.slaveCmd.Process.Signal(s); err != nil {
			mp.debugf("signal failed (%s), assuming slave process died unexpectedly", err)
			os.Exit(1)
		}
	}
}

func (mp *master) retreiveFileDescriptors() error {
	mp.slaveExtraFiles = make([]*os.File, len(mp.Config.Addresses))
	for i, addr := range mp.Config.Addresses {
		a, err := net.ResolveTCPAddr("tcp", addr)
		if err != nil {
			return fmt.Errorf("Invalid address %s (%s)", addr, err)
		}
		l, err := net.ListenTCP("tcp", a)
		if err != nil {
			return err
		}
		f, err := l.File()
		if err != nil {
			return fmt.Errorf("Failed to retreive fd for: %s (%s)", addr, err)
		}
		if err := l.Close(); err != nil {
			return fmt.Errorf("Failed to close listener for: %s (%s)", addr, err)
		}
		mp.slaveExtraFiles[i] = f
	}
	return nil
}

//fetchLoop is run in a goroutine
func (mp *master) fetchLoop() {
	min := mp.Config.MinFetchInterval
	time.Sleep(min)
	for {
		t0 := time.Now()
		mp.fetch()
		//duration fetch of fetch
		diff := time.Now().Sub(t0)
		if diff < min {
			delay := min - diff
			//ensures at least MinFetchInterval delay.
			//should be throttled by the fetcher!
			time.Sleep(delay)
		}
	}
}

func (mp *master) fetch() {
	if mp.restarting {
		return //skip if restarting
	}
	if mp.printCheckUpdate {
		mp.debugf("checking for updates...")
	}
	reader, err := mp.Fetcher.Fetch()
	if err != nil {
		mp.debugf("failed to get latest version: %s", err)
		return
	}
	if reader == nil {
		if mp.printCheckUpdate {
			mp.debugf("no updates")
		}
		mp.printCheckUpdate = false
		return //fetcher has explicitly said there are no updates
	}
	mp.printCheckUpdate = true
	mp.debugf("streaming update...")
	//optional closer
	if closer, ok := reader.(io.Closer); ok {
		defer closer.Close()
	}
	tmpBin, err := os.OpenFile(tmpBinPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		mp.warnf("failed to open temp binary: %s", err)
		return
	}
	defer func() {
		tmpBin.Close()
		os.Remove(tmpBinPath)
	}()
	//tee off to sha1
	hash := sha1.New()
	reader = io.TeeReader(reader, hash)
	//write to a temp file
	_, err = io.Copy(tmpBin, reader)
	if err != nil {
		mp.warnf("failed to write temp binary: %s", err)
		return
	}
	//compare hash
	newHash := hash.Sum(nil)
	if bytes.Equal(mp.binHash, newHash) {
		mp.debugf("hash match - skip")
		return
	}
	//copy permissions
	if err := chmod(tmpBin, mp.binPerms); err != nil {
		mp.warnf("failed to make temp binary executable: %s", err)
		return
	}
	if err := chown(tmpBin, uid, gid); err != nil {
		mp.warnf("failed to change owner of binary: %s", err)
		return
	}
	if _, err := tmpBin.Stat(); err != nil {
		mp.warnf("failed to stat temp binary: %s", err)
		return
	}
	tmpBin.Close()
	if _, err := os.Stat(tmpBinPath); err != nil {
		mp.warnf("failed to stat temp binary by path: %s", err)
		return
	}
	if mp.Config.PreUpgrade != nil {
		if err := mp.Config.PreUpgrade(tmpBinPath); err != nil {
			mp.warnf("user cancelled upgrade: %s", err)
			return
		}
	}
	//overseer sanity check, dont replace our good binary with a non-executable file
	tokenIn := token()
	cmd := exec.Command(tmpBinPath)
	cmd.Env = []string{envBinCheck + "=" + tokenIn}
	returned := false
	go func() {
		time.Sleep(5 * time.Second)
		if !returned {
			mp.warnf("sanity check against fetched executable timed-out, check overseer is running")
			if cmd.Process != nil {
				cmd.Process.Kill()
			}
		}
	}()
	tokenOut, err := cmd.CombinedOutput()
	returned = true
	if err != nil {
		mp.warnf("failed to run temp binary: %s (%s) output \"%s\"", err, tmpBinPath, tokenOut)
		return
	}
	if tokenIn != string(tokenOut) {
		mp.warnf("sanity check failed")
		return
	}
	//overwrite!
	if err := move(mp.binPath, tmpBinPath); err != nil {
		mp.warnf("failed to overwrite binary: %s", err)
		return
	}
	mp.debugf("upgraded binary (%x -> %x)", mp.binHash[:12], newHash[:12])
	mp.binHash = newHash
	//binary successfully replaced
	if !mp.Config.NoRestartAfterFetch {
		mp.triggerRestart()
	}
	//and keep fetching...
	return
}

func (mp *master) triggerRestart() {
	if mp.restarting {
		mp.debugf("already graceful restarting")
		return //skip
	} else if mp.slaveCmd == nil || mp.restarting {
		mp.debugf("no slave process")
		return //skip
	}
	mp.debugf("graceful restart triggered")
	mp.restarting = true
	mp.awaitingUSR1 = true
	mp.signalledAt = time.Now()
	mp.sendSignal(mp.Config.RestartSignal) //ask nicely to terminate
	select {
	case <-mp.restarted:
		//success
		mp.debugf("restart success")
	case <-time.After(mp.TerminateTimeout):
		//times up mr. process, we did ask nicely!
		mp.debugf("graceful timeout, forcing exit")
		mp.sendSignal(os.Kill)
	}
}

//not a real fork
func (mp *master) forkLoop() error {
	//loop, restart command
	for {
		if err := mp.fork(); err != nil {
			return err
		}
	}
}

func (mp *master) fork() error {
	mp.debugf("starting %s", mp.binPath)
	cmd := exec.Command(mp.binPath)
	//mark this new process as the "active" slave process.
	//this process is assumed to be holding the socket files.
	mp.slaveCmd = cmd
	mp.slaveID++
	//provide the slave process with some state
	e := os.Environ()
	e = append(e, envBinID+"="+hex.EncodeToString(mp.binHash))
	e = append(e, envBinPath+"="+mp.binPath)
	e = append(e, envSlaveID+"="+strconv.Itoa(mp.slaveID))
	e = append(e, envIsSlave+"=1")
	e = append(e, envNumFDs+"="+strconv.Itoa(len(mp.slaveExtraFiles)))
	cmd.Env = e
	//inherit master args/stdfiles
	cmd.Args = os.Args
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	//include socket files
	cmd.ExtraFiles = mp.slaveExtraFiles
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("Failed to start slave process: %s", err)
	}
	//was scheduled to restart, notify success
	if mp.restarting {
		mp.restartedAt = time.Now()
		mp.restarting = false
		mp.restarted <- true
	}
	//convert wait into channel
	cmdwait := make(chan error)
	go func() {
		cmdwait <- cmd.Wait()
	}()
	//wait....
	select {
	case err := <-cmdwait:
		//program exited before releasing descriptors
		//proxy exit code out to master
		code := 0
		if err != nil {
			code = 1
			if exiterr, ok := err.(*exec.ExitError); ok {
				if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
					code = status.ExitStatus()
				}
			}
		}
		mp.debugf("prog exited with %d", code)
		//if a restarts are disabled or if it was an
		//unexpected creash, proxy this exit straight
		//through to the main process
		if mp.NoRestart || !mp.restarting {
			os.Exit(code)
		}
	case <-mp.descriptorsReleased:
		//if descriptors are released, the program
		//has yielded control of its sockets and
		//a parallel instance of the program can be
		//started safely. it should serve state.Listeners
		//to ensure downtime is kept at <1sec. The previous
		//cmd.Wait() will still be consumed though the
		//result will be discarded.
	}
	return nil
}

func (mp *master) debugf(f string, args ...interface{}) {
	if mp.Config.Debug {
		log.Printf("[overseer master] "+f, args...)
	}
}

func (mp *master) warnf(f string, args ...interface{}) {
	if mp.Config.Debug || !mp.Config.NoWarn {
		log.Printf("[overseer master] "+f, args...)
	}
}

func token() string {
	buff := make([]byte, 8)
	rand.Read(buff)
	return hex.EncodeToString(buff)
}
<|endoftext|>


<commit_before>package proto3

import (
	"bytes"
	"errors"
	"fmt"
)

type ImportType string
type NameType string
type TagType uint8
type FieldType uint8
type FieldRule uint8

// Rules that can be applied to message fields
// https://developers.google.com/protocol-buffers/docs/proto3#specifying-field-rules
const (
	NONE FieldRule = iota
	REQUIRED
	OPTIONAL
	REPEATED
)

// Built-in field types
// https://developers.google.com/protocol-buffers/docs/proto3#scalar
const (
	DOUBLE_TYPE FieldType = iota
	FLOAT_TYPE
	INT32_TYPE
	INT64_TYPE
	UINT32_TYPE
	UINT64_TYPE
	SINT32_TYPE
	SINT64_TYPE
	FIXED32_TYPE
	FIXED64_TYPE
	SFIXED32_TYPE
	SFIXED64_TYPE
	BOOL_TYPE
	STRING_TYPE
	BYTES_TYPE
)

// Reserved describes a tag that can be written to a Protobuf.
type Reserved interface {
	Validate() error
	Write() (string, error)
}

// Field describes a Protobuf message field.
type Field interface {
	Validate() error
	Write() (string, error)
}

// Spec represents a top-level Protobuf specification.
type Spec struct {
	Package  string       // https://developers.google.com/protocol-buffers/docs/proto3#packages
	Imports  []ImportType // https://developers.google.com/protocol-buffers/docs/proto3#importing-definitions
	Messages []Message
}

// Message is a single Protobuf message definition.
type Message struct {
	Name           string
	Messages       []Message
	ReservedValues []Reserved
	Fields         []Field
	Enums          []Enum
}

// ReservedName is a field name that is reserved within a message type and cannot be reused.
// https://developers.google.com/protocol-buffers/docs/proto3#reserved
type ReservedName struct {
	Name NameType
}

// ReservedTagValue is a single field tag value that is reserved within a message type and cannot be reused.
// https://developers.google.com/protocol-buffers/docs/proto3#reserved
type ReservedTagValue struct {
	Tag TagType
}

// ReservedTagRange is a range of numeric tag values that are reserved within a message type and cannot be reused.
// https://developers.google.com/protocol-buffers/docs/proto3#reserved
type ReservedTagRange struct {
	LowerTag TagType
	UpperTag TagType
}

// CustomField is a message field with an unchecked, custom type. This can be used to define fields that
// use imported types.
type CustomField struct {
	Name    NameType
	Tag     TagType
	Rule    FieldRule
	Comment string
	Typing  string
}

// ScalarField is a message field that uses a built-in protobuf type.
type ScalarField struct {
	Name    NameType
	Tag     TagType
	Rule    FieldRule
	Comment string
	Typing  FieldType
}

// MapField is a message field that maps built-in protobuf type as key-value pairs
// https://developers.google.com/protocol-buffers/docs/proto3#maps
type MapField struct {
	Name        NameType
	Tag         TagType
	Rule        FieldRule
	Comment     string
	KeyTyping   FieldType
	ValueTyping FieldType
}

// CustomMapField is a message field that maps between a built-in protobuf type as
// the key and a custom type as the value.
// https://developers.google.com/protocol-buffers/docs/proto3#maps
type CustomMapField struct {
	Name        NameType
	Tag         TagType
	Rule        FieldRule
	Comment     string
	KeyTyping   FieldType
	ValueTyping string
}

// Enum defines an enumeration type of a set of values.
// https://developers.google.com/protocol-buffers/docs/proto3#enum
type Enum struct {
	Name       NameType
	Values     []EnumValue
	AllowAlias bool
}

// EnumValue describes a single enumerated value within an enumeration.
// https://developers.google.com/protocol-buffers/docs/proto3#enum
type EnumValue struct {
	Name    NameType
	Tag     TagType
	Comment string
}

// WRITERS

// Write turns the specification into a string.
func (s *Spec) Write() (string, error) {
	if err := s.Validate(); err != nil {
		return "", err
	}

	var buffer bytes.Buffer
	buffer.WriteString("syntax = \"proto3\";\n")
	if len(s.Package) > 0 {
		buffer.WriteString(fmt.Sprintf("package %s;\n", s.Package))
	}
	for _, importPackage := range s.Imports {
		buffer.WriteString(fmt.Sprintf("import \"%s\";\n", importPackage))
	}
	for _, msg := range s.Messages {
		msgSpec, err := msg.Write(0) // write message at level zero (0)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("\n%s\n", msgSpec))
	}
	return buffer.String(), nil
}

// Write the message specification as a string at a given indentation level.
func (m *Message) Write(level int) (string, error) {
	if err := m.Validate(); err != nil {
		return "", err
	}

	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("%smessage %s {\n", indentLevel(level), m.Name))

	// NESTED MESSAGE TYPES
	for _, msg := range m.Messages {
		msgSpec, err := msg.Write(level + 1)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s\n", msgSpec))
	}

	// ENUMS
	for _, v := range m.Enums {
		v, err := v.Write(level + 1)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s\n", v))
	}

	// RESERVED TAGS
	for _, reservedValue := range m.ReservedValues {
		v, err := reservedValue.Write()
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%sreserved %s;\n", indentLevel(level+1), v))
	}

	// FIELDS
	for _, v := range m.Fields {
		v, err := v.Write()
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s%s\n", indentLevel(level+1), v))
	}

	buffer.WriteString(fmt.Sprintf("%s}\n", indentLevel(level)))
	return buffer.String(), nil
}

// Write a ReservedName as a string
func (r ReservedName) Write() (string, error) {
	return fmt.Sprintf("\"%s\"", r.Name), nil
}

// Write a ReservedTagValue as a string
func (r ReservedTagValue) Write() (string, error) {
	return fmt.Sprintf("%d", r.Tag), nil
}

// Write a ReservedTagRange as a string
func (r ReservedTagRange) Write() (string, error) {
	return fmt.Sprintf("%d to %d", r.LowerTag, r.UpperTag), nil
}

// Write a CustomField as a string
func (c CustomField) Write() (string, error) {
	v := fmt.Sprintf("%s%s %s = %d;", c.Rule.Write(), c.Typing, c.Name, c.Tag)
	if c.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, c.Comment)
	}
	return v, nil
}

// Write a ScalarField as a string
func (s ScalarField) Write() (string, error) {
	v := fmt.Sprintf("%s%s %s = %d;", s.Rule.Write(), s.Typing.Write(), s.Name, s.Tag)
	if s.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, s.Comment)
	}
	return v, nil
}

// Write a MapField as a string
func (m MapField) Write() (string, error) {
	v := fmt.Sprintf("%smap<%s, %s> %s = %d;", m.Rule.Write(), m.KeyTyping.Write(), m.ValueTyping.Write(), m.Name, m.Tag)
	if m.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, m.Comment)
	}
	return v, nil
}

// Write a CustomMapField as a string
func (c CustomMapField) Write() (string, error) {
	v := fmt.Sprintf("%smap<%s, %s> %s = %d;", c.Rule.Write(), c.KeyTyping.Write(), c.ValueTyping, c.Name, c.Tag)
	if c.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, c.Comment)
	}
	return v, nil
}

// Write a CustomMapField as a string
func (e Enum) Write(level int) (string, error) {
	v := fmt.Sprintf("%senum %s {\n", indentLevel(level), e.Name)
	if e.AllowAlias {
		v = fmt.Sprintf("%s%soption allow_alias = true;\n", v, indentLevel(level+1))
	}
	for _, enumValue := range e.Values {
		v = fmt.Sprintf("%s%s%s = %d;", v, indentLevel(level+1), enumValue.Name, enumValue.Tag)
		if enumValue.Comment != "" {
			v = fmt.Sprintf("%s   // %s", v, enumValue.Comment)
		}
		v = fmt.Sprintf("%s\n", v)
	}
	v = fmt.Sprintf("%s%s}", v, indentLevel(level))
	return v, nil
}

// Write a FieldRule as a string
func (f *FieldRule) Write() string {
	switch *f {
	case NONE:
		return ""
	case REQUIRED:
		return "required "
	case OPTIONAL:
		return "optional "
	case REPEATED:
		return "repeated "
	default:
		return ""
	}
}

// Write a FieldType as a string
func (f *FieldType) Write() string {
	switch *f {
	case DOUBLE_TYPE:
		return "double"
	case FLOAT_TYPE:
		return "float"
	case INT32_TYPE:
		return "int32"
	case INT64_TYPE:
		return "int64"
	case UINT32_TYPE:
		return "uint32"
	case UINT64_TYPE:
		return "uint64"
	case SINT32_TYPE:
		return "sint32"
	case SINT64_TYPE:
		return "sint64"
	case FIXED32_TYPE:
		return "fixed32"
	case FIXED64_TYPE:
		return "fixed64"
	case SFIXED32_TYPE:
		return "sfixed32"
	case SFIXED64_TYPE:
		return "sfixed64"
	case BOOL_TYPE:
		return "bool"
	case STRING_TYPE:
		return "string"
	case BYTES_TYPE:
		return "bytes"
	default:
		return ""
	}
}

// VALIDATORS

// Validate spec
func (s *Spec) Validate() error {
	if len(s.Messages) == 0 {
		return errors.New("Spec must contain at least one message")
	}
	for _, msg := range s.Messages {
		if err := msg.Validate(); err != nil {
			return err
		}
	}
	return nil
}

// Validate the attributes of a message, including all children that can be validated individually.
func (m Message) Validate() error {
	if m.Name == "" {
		return errors.New("Message name cannot be empty")
	}
	for _, v := range m.Fields {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	for _, v := range m.Messages {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	for _, v := range m.ReservedValues {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	for _, v := range m.Enums {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	return nil
}

// Validate field attributes
func (s ScalarField) Validate() error {
	if s.Name == "" {
		return errors.New("Scalar field must have a non-empty name")
	}
	return nil
}

// Validate field attributes
func (s ReservedName) Validate() error {
	if s.Name == "" {
		return errors.New("ReservedName field must have a non-empty name")
	}
	return nil
}

// Validate field attributes
func (s ReservedTagValue) Validate() error {
	return nil
}

// Validate field attributes
func (s ReservedTagRange) Validate() error {
	if s.LowerTag < 0 {
		return errors.New("ReservedTagRange lower-tag must be greater-than-or-equal to zero")
	}
	if s.LowerTag >= s.UpperTag {
		return errors.New("ReservedTagRange upper-tag must be greater-than lower-tag")
	}
	return nil
}

// Validate field attributes
func (c CustomField) Validate() error {
	if c.Name == "" {
		return errors.New("CustomField name must have non-empty name")
	}
	if c.Tag < 0 {
		return errors.New("CustomField must have positive integer for tag")
	}
	return nil
}

// Validate field attributes
func (c CustomMapField) Validate() error {
	if c.Name == "" {
		return errors.New("CustomMapField name must have non-empty name")
	}
	if c.KeyTyping < 0 || c.KeyTyping == DOUBLE_TYPE || c.KeyTyping == FLOAT_TYPE || c.KeyTyping == BYTES_TYPE {
		return fmt.Errorf("Map field %s must use a scalar integral or string type for the map key", c.Name)
	}
	if c.Rule == REPEATED {
		return errors.New("CustomMapField cannot use repeated rule")
	}
	return nil
}

func (m MapField) Validate() error {
	if m.Name == "" {
		return errors.New("MapField must have a non-empty name")
	}
	if m.KeyTyping < 0 || m.KeyTyping == DOUBLE_TYPE || m.KeyTyping == FLOAT_TYPE || m.KeyTyping == BYTES_TYPE {
		return fmt.Errorf("Map field %s must use a scalar integral or string type for the map key", m.Name)
	}
	if m.ValueTyping < 0 {
		return fmt.Errorf("Map field %s must have a type specified for the map value", m.Name)
	}
	if m.Rule == REPEATED {
		return errors.New("MapField cannot use repeated rule")
	}
	return nil
}

func (e *Enum) Validate() error {
	if e.Name == "" {
		return errors.New("Enum must have a non-empty name")
	}
	if len(e.Values) == 0 {
		return errors.New("Enum must have non-empty set of values")
	}
	return nil
}

// FORMATTING

func indentLevel(level int) string {
	var buffer bytes.Buffer
	for i := 0; i < level; i++ {
		buffer.WriteString("  ")
	}
	return buffer.String()
}
<commit_msg>Allow Enum declarations outside the scope of a message, at the root of the spec<commit_after>package proto3

import (
	"bytes"
	"errors"
	"fmt"
)

type ImportType string
type NameType string
type TagType uint8
type FieldType uint8
type FieldRule uint8

// Rules that can be applied to message fields
// https://developers.google.com/protocol-buffers/docs/proto3#specifying-field-rules
const (
	NONE FieldRule = iota
	REQUIRED
	OPTIONAL
	REPEATED
)

// Built-in field types
// https://developers.google.com/protocol-buffers/docs/proto3#scalar
const (
	DOUBLE_TYPE FieldType = iota
	FLOAT_TYPE
	INT32_TYPE
	INT64_TYPE
	UINT32_TYPE
	UINT64_TYPE
	SINT32_TYPE
	SINT64_TYPE
	FIXED32_TYPE
	FIXED64_TYPE
	SFIXED32_TYPE
	SFIXED64_TYPE
	BOOL_TYPE
	STRING_TYPE
	BYTES_TYPE
)

// Reserved describes a tag that can be written to a Protobuf.
type Reserved interface {
	Validate() error
	Write() (string, error)
}

// Field describes a Protobuf message field.
type Field interface {
	Validate() error
	Write() (string, error)
}

// Spec represents a top-level Protobuf specification.
type Spec struct {
	Package  string       // https://developers.google.com/protocol-buffers/docs/proto3#packages
	Imports  []ImportType // https://developers.google.com/protocol-buffers/docs/proto3#importing-definitions
	Messages []Message
	Enums    []Enum
}

// Message is a single Protobuf message definition.
type Message struct {
	Name           string
	Messages       []Message
	ReservedValues []Reserved
	Fields         []Field
	Enums          []Enum
}

// ReservedName is a field name that is reserved within a message type and cannot be reused.
// https://developers.google.com/protocol-buffers/docs/proto3#reserved
type ReservedName struct {
	Name NameType
}

// ReservedTagValue is a single field tag value that is reserved within a message type and cannot be reused.
// https://developers.google.com/protocol-buffers/docs/proto3#reserved
type ReservedTagValue struct {
	Tag TagType
}

// ReservedTagRange is a range of numeric tag values that are reserved within a message type and cannot be reused.
// https://developers.google.com/protocol-buffers/docs/proto3#reserved
type ReservedTagRange struct {
	LowerTag TagType
	UpperTag TagType
}

// CustomField is a message field with an unchecked, custom type. This can be used to define fields that
// use imported types.
type CustomField struct {
	Name    NameType
	Tag     TagType
	Rule    FieldRule
	Comment string
	Typing  string
}

// ScalarField is a message field that uses a built-in protobuf type.
type ScalarField struct {
	Name    NameType
	Tag     TagType
	Rule    FieldRule
	Comment string
	Typing  FieldType
}

// MapField is a message field that maps built-in protobuf type as key-value pairs
// https://developers.google.com/protocol-buffers/docs/proto3#maps
type MapField struct {
	Name        NameType
	Tag         TagType
	Rule        FieldRule
	Comment     string
	KeyTyping   FieldType
	ValueTyping FieldType
}

// CustomMapField is a message field that maps between a built-in protobuf type as
// the key and a custom type as the value.
// https://developers.google.com/protocol-buffers/docs/proto3#maps
type CustomMapField struct {
	Name        NameType
	Tag         TagType
	Rule        FieldRule
	Comment     string
	KeyTyping   FieldType
	ValueTyping string
}

// Enum defines an enumeration type of a set of values.
// https://developers.google.com/protocol-buffers/docs/proto3#enum
type Enum struct {
	Name       NameType
	Values     []EnumValue
	AllowAlias bool
}

// EnumValue describes a single enumerated value within an enumeration.
// https://developers.google.com/protocol-buffers/docs/proto3#enum
type EnumValue struct {
	Name    NameType
	Tag     TagType
	Comment string
}

// WRITERS

// Write turns the specification into a string.
func (s *Spec) Write() (string, error) {
	if err := s.Validate(); err != nil {
		return "", err
	}

	var buffer bytes.Buffer
	buffer.WriteString("syntax = \"proto3\";\n")
	if len(s.Package) > 0 {
		buffer.WriteString(fmt.Sprintf("package %s;\n", s.Package))
	}
	for _, importPackage := range s.Imports {
		buffer.WriteString(fmt.Sprintf("import \"%s\";\n", importPackage))
	}

	for _, v := range s.Enums {
		v, err := v.Write(0)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s\n", v))
	}

	for _, msg := range s.Messages {
		msgSpec, err := msg.Write(0) // write message at level zero (0)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("\n%s\n", msgSpec))
	}
	return buffer.String(), nil
}

// Write the message specification as a string at a given indentation level.
func (m *Message) Write(level int) (string, error) {
	if err := m.Validate(); err != nil {
		return "", err
	}

	var buffer bytes.Buffer
	buffer.WriteString(fmt.Sprintf("%smessage %s {\n", indentLevel(level), m.Name))

	// NESTED MESSAGE TYPES
	for _, msg := range m.Messages {
		msgSpec, err := msg.Write(level + 1)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s\n", msgSpec))
	}

	// ENUMS
	for _, v := range m.Enums {
		v, err := v.Write(level + 1)
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s\n", v))
	}

	// RESERVED TAGS
	for _, reservedValue := range m.ReservedValues {
		v, err := reservedValue.Write()
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%sreserved %s;\n", indentLevel(level+1), v))
	}

	// FIELDS
	for _, v := range m.Fields {
		v, err := v.Write()
		if err != nil {
			return "", err
		}
		buffer.WriteString(fmt.Sprintf("%s%s\n", indentLevel(level+1), v))
	}

	buffer.WriteString(fmt.Sprintf("%s}\n", indentLevel(level)))
	return buffer.String(), nil
}

// Write a ReservedName as a string
func (r ReservedName) Write() (string, error) {
	return fmt.Sprintf("\"%s\"", r.Name), nil
}

// Write a ReservedTagValue as a string
func (r ReservedTagValue) Write() (string, error) {
	return fmt.Sprintf("%d", r.Tag), nil
}

// Write a ReservedTagRange as a string
func (r ReservedTagRange) Write() (string, error) {
	return fmt.Sprintf("%d to %d", r.LowerTag, r.UpperTag), nil
}

// Write a CustomField as a string
func (c CustomField) Write() (string, error) {
	v := fmt.Sprintf("%s%s %s = %d;", c.Rule.Write(), c.Typing, c.Name, c.Tag)
	if c.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, c.Comment)
	}
	return v, nil
}

// Write a ScalarField as a string
func (s ScalarField) Write() (string, error) {
	v := fmt.Sprintf("%s%s %s = %d;", s.Rule.Write(), s.Typing.Write(), s.Name, s.Tag)
	if s.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, s.Comment)
	}
	return v, nil
}

// Write a MapField as a string
func (m MapField) Write() (string, error) {
	v := fmt.Sprintf("%smap<%s, %s> %s = %d;", m.Rule.Write(), m.KeyTyping.Write(), m.ValueTyping.Write(), m.Name, m.Tag)
	if m.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, m.Comment)
	}
	return v, nil
}

// Write a CustomMapField as a string
func (c CustomMapField) Write() (string, error) {
	v := fmt.Sprintf("%smap<%s, %s> %s = %d;", c.Rule.Write(), c.KeyTyping.Write(), c.ValueTyping, c.Name, c.Tag)
	if c.Comment != "" {
		v = fmt.Sprintf("%s   // %s", v, c.Comment)
	}
	return v, nil
}

// Write a CustomMapField as a string
func (e Enum) Write(level int) (string, error) {
	v := fmt.Sprintf("%senum %s {\n", indentLevel(level), e.Name)
	if e.AllowAlias {
		v = fmt.Sprintf("%s%soption allow_alias = true;\n", v, indentLevel(level+1))
	}
	for _, enumValue := range e.Values {
		v = fmt.Sprintf("%s%s%s = %d;", v, indentLevel(level+1), enumValue.Name, enumValue.Tag)
		if enumValue.Comment != "" {
			v = fmt.Sprintf("%s   // %s", v, enumValue.Comment)
		}
		v = fmt.Sprintf("%s\n", v)
	}
	v = fmt.Sprintf("%s%s}", v, indentLevel(level))
	return v, nil
}

// Write a FieldRule as a string
func (f *FieldRule) Write() string {
	switch *f {
	case NONE:
		return ""
	case REQUIRED:
		return "required "
	case OPTIONAL:
		return "optional "
	case REPEATED:
		return "repeated "
	default:
		return ""
	}
}

// Write a FieldType as a string
func (f *FieldType) Write() string {
	switch *f {
	case DOUBLE_TYPE:
		return "double"
	case FLOAT_TYPE:
		return "float"
	case INT32_TYPE:
		return "int32"
	case INT64_TYPE:
		return "int64"
	case UINT32_TYPE:
		return "uint32"
	case UINT64_TYPE:
		return "uint64"
	case SINT32_TYPE:
		return "sint32"
	case SINT64_TYPE:
		return "sint64"
	case FIXED32_TYPE:
		return "fixed32"
	case FIXED64_TYPE:
		return "fixed64"
	case SFIXED32_TYPE:
		return "sfixed32"
	case SFIXED64_TYPE:
		return "sfixed64"
	case BOOL_TYPE:
		return "bool"
	case STRING_TYPE:
		return "string"
	case BYTES_TYPE:
		return "bytes"
	default:
		return ""
	}
}

// VALIDATORS

// Validate spec
func (s *Spec) Validate() error {
	if len(s.Messages) == 0 {
		return errors.New("Spec must contain at least one message")
	}
	for _, msg := range s.Messages {
		if err := msg.Validate(); err != nil {
			return err
		}
	}
	for _, v := range s.Enums {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	return nil
}

// Validate the attributes of a message, including all children that can be validated individually.
func (m Message) Validate() error {
	if m.Name == "" {
		return errors.New("Message name cannot be empty")
	}
	for _, v := range m.Fields {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	for _, v := range m.Messages {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	for _, v := range m.ReservedValues {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	for _, v := range m.Enums {
		if err := v.Validate(); err != nil {
			return err
		}
	}
	return nil
}

// Validate field attributes
func (s ScalarField) Validate() error {
	if s.Name == "" {
		return errors.New("Scalar field must have a non-empty name")
	}
	return nil
}

// Validate field attributes
func (s ReservedName) Validate() error {
	if s.Name == "" {
		return errors.New("ReservedName field must have a non-empty name")
	}
	return nil
}

// Validate field attributes
func (s ReservedTagValue) Validate() error {
	return nil
}

// Validate field attributes
func (s ReservedTagRange) Validate() error {
	if s.LowerTag < 0 {
		return errors.New("ReservedTagRange lower-tag must be greater-than-or-equal to zero")
	}
	if s.LowerTag >= s.UpperTag {
		return errors.New("ReservedTagRange upper-tag must be greater-than lower-tag")
	}
	return nil
}

// Validate field attributes
func (c CustomField) Validate() error {
	if c.Name == "" {
		return errors.New("CustomField name must have non-empty name")
	}
	if c.Tag < 0 {
		return errors.New("CustomField must have positive integer for tag")
	}
	return nil
}

// Validate field attributes
func (c CustomMapField) Validate() error {
	if c.Name == "" {
		return errors.New("CustomMapField name must have non-empty name")
	}
	if c.KeyTyping < 0 || c.KeyTyping == DOUBLE_TYPE || c.KeyTyping == FLOAT_TYPE || c.KeyTyping == BYTES_TYPE {
		return fmt.Errorf("Map field %s must use a scalar integral or string type for the map key", c.Name)
	}
	if c.Rule == REPEATED {
		return errors.New("CustomMapField cannot use repeated rule")
	}
	return nil
}

func (m MapField) Validate() error {
	if m.Name == "" {
		return errors.New("MapField must have a non-empty name")
	}
	if m.KeyTyping < 0 || m.KeyTyping == DOUBLE_TYPE || m.KeyTyping == FLOAT_TYPE || m.KeyTyping == BYTES_TYPE {
		return fmt.Errorf("Map field %s must use a scalar integral or string type for the map key", m.Name)
	}
	if m.ValueTyping < 0 {
		return fmt.Errorf("Map field %s must have a type specified for the map value", m.Name)
	}
	if m.Rule == REPEATED {
		return errors.New("MapField cannot use repeated rule")
	}
	return nil
}

func (e *Enum) Validate() error {
	if e.Name == "" {
		return errors.New("Enum must have a non-empty name")
	}
	if len(e.Values) == 0 {
		return errors.New("Enum must have non-empty set of values")
	}
	return nil
}

// FORMATTING

func indentLevel(level int) string {
	var buffer bytes.Buffer
	for i := 0; i < level; i++ {
		buffer.WriteString("  ")
	}
	return buffer.String()
}
<|endoftext|>


<commit_before>// Package provider holds the different provider implementation.
package provider

import (
	"fmt"
	"strings"
	"text/template"
	"time"

	"errors"
	"github.com/BurntSushi/ty/fun"
	log "github.com/Sirupsen/logrus"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
	"github.com/docker/libkv"
	"github.com/docker/libkv/store"
	"github.com/emilevauge/backoff"
)

// Kv holds common configurations of key-value providers.
type Kv struct {
	BaseProvider `mapstructure:",squash"`
	Endpoint     string     `description:"Comma sepparated server endpoints"`
	Prefix       string     `description:"Prefix used for KV store"`
	TLS          *ClientTLS `description:"Enable TLS support"`
	storeType    store.Backend
	kvclient     store.Store
}

func (provider *Kv) createStore() (store.Store, error) {
	storeConfig := &store.Config{
		ConnectionTimeout: 30 * time.Second,
		Bucket:            "traefik",
	}

	if provider.TLS != nil {
		var err error
		storeConfig.TLS, err = provider.TLS.CreateTLSConfig()
		if err != nil {
			return nil, err
		}
	}
	return libkv.NewStore(
		provider.storeType,
		strings.Split(provider.Endpoint, ","),
		storeConfig,
	)
}

func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
	operation := func() error {
		events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}))
		if err != nil {
			return fmt.Errorf("Failed to KV WatchTree: %v", err)
		}
		for {
			select {
			case <-stop:
				return nil
			case _, ok := <-events:
				if !ok {
					return errors.New("watchtree channel closed")
				}
				configuration := provider.loadConfig()
				if configuration != nil {
					configurationChan <- types.ConfigMessage{
						ProviderName:  string(provider.storeType),
						Configuration: configuration,
					}
				}
			}
		}
	}

	notify := func(err error, time time.Duration) {
		log.Errorf("KV connection error: %+v, retrying in %s", err, time)
	}
	err := backoff.RetryNotify(operation, backoff.NewJobBackOff(backoff.NewExponentialBackOff()), notify)
	if err != nil {
		return fmt.Errorf("Cannot connect to KV server: %v", err)
	}
	return nil
}

func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
	provider.Constraints = append(provider.Constraints, constraints...)
	storeConfig := &store.Config{
		ConnectionTimeout: 30 * time.Second,
		Bucket:            "traefik",
	}

	if provider.TLS != nil {
		caPool := x509.NewCertPool()

		if provider.TLS.CA != "" {
			ca, err := ioutil.ReadFile(provider.TLS.CA)

			if err != nil {
				return fmt.Errorf("Failed to read CA. %s", err)
			}

			caPool.AppendCertsFromPEM(ca)
		}

		cert, err := tls.LoadX509KeyPair(provider.TLS.Cert, provider.TLS.Key)

		if err != nil {
			return fmt.Errorf("Failed to load TLS keypair: %v", err)
		}

		storeConfig.TLS = &tls.Config{
			Certificates:       []tls.Certificate{cert},
			RootCAs:            caPool,
			InsecureSkipVerify: provider.TLS.InsecureSkipVerify,
		}
	}

	operation := func() error {
		if _, err := provider.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
			return fmt.Errorf("Failed to test KV store connection: %v", err)
		}
		if provider.Watch {
			pool.Go(func(stop chan bool) {
				err := provider.watchKv(configurationChan, provider.Prefix, stop)
				if err != nil {
					log.Errorf("Cannot watch KV store: %v", err)
				}
			})
		}
		configuration := provider.loadConfig()
		configurationChan <- types.ConfigMessage{
			ProviderName:  string(provider.storeType),
			Configuration: configuration,
		}
		return nil
	}
	notify := func(err error, time time.Duration) {
		log.Errorf("KV connection error: %+v, retrying in %s", err, time)
	}
	err := backoff.RetryNotify(operation, backoff.NewJobBackOff(backoff.NewExponentialBackOff()), notify)
	if err != nil {
		return fmt.Errorf("Cannot connect to KV server: %v", err)
	}
	return nil
}

func (provider *Kv) loadConfig() *types.Configuration {
	templateObjects := struct {
		Prefix string
	}{
		// Allow `/traefik/alias` to superesede `provider.Prefix`
		strings.TrimSuffix(provider.get(provider.Prefix, provider.Prefix+"/alias"), "/"),
	}

	var KvFuncMap = template.FuncMap{
		"List":        provider.list,
		"ListServers": provider.listServers,
		"Get":         provider.get,
		"SplitGet":    provider.splitGet,
		"Last":        provider.last,
	}

	configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
	if err != nil {
		log.Error(err)
	}

	for key, frontend := range configuration.Frontends {
		if _, ok := configuration.Backends[frontend.Backend]; ok == false {
			delete(configuration.Frontends, key)
		}
	}

	return configuration
}

func (provider *Kv) list(keys ...string) []string {
	joinedKeys := strings.Join(keys, "")
	keysPairs, err := provider.kvclient.List(joinedKeys)
	if err != nil {
		log.Errorf("Error getting keys %s %s ", joinedKeys, err)
		return nil
	}
	directoryKeys := make(map[string]string)
	for _, key := range keysPairs {
		directory := strings.Split(strings.TrimPrefix(key.Key, joinedKeys), "/")[0]
		directoryKeys[directory] = joinedKeys + directory
	}
	return fun.Values(directoryKeys).([]string)
}

func (provider *Kv) listServers(backend string) []string {
	serverNames := provider.list(backend, "/servers/")
	return fun.Filter(func(serverName string) bool {
		return provider.checkConstraints(serverName, "/tags")
	}, serverNames).([]string)
}

func (provider *Kv) get(defaultValue string, keys ...string) string {
	joinedKeys := strings.Join(keys, "")
	keyPair, err := provider.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
	if err != nil {
		log.Warnf("Error getting key %s %s, setting default %s", joinedKeys, err, defaultValue)
		return defaultValue
	} else if keyPair == nil {
		log.Warnf("Error getting key %s, setting default %s", joinedKeys, defaultValue)
		return defaultValue
	}
	return string(keyPair.Value)
}

func (provider *Kv) splitGet(keys ...string) []string {
	joinedKeys := strings.Join(keys, "")
	keyPair, err := provider.kvclient.Get(joinedKeys)
	if err != nil {
		log.Warnf("Error getting key %s %s, setting default empty", joinedKeys, err)
		return []string{}
	} else if keyPair == nil {
		log.Warnf("Error getting key %s, setting default %empty", joinedKeys)
		return []string{}
	}
	return strings.Split(string(keyPair.Value), ",")
}

func (provider *Kv) last(key string) string {
	splittedKey := strings.Split(key, "/")
	return splittedKey[len(splittedKey)-1]
}

func (provider *Kv) checkConstraints(keys ...string) bool {
	joinedKeys := strings.Join(keys, "")
	keyPair, err := provider.kvclient.Get(joinedKeys)

	value := ""
	if err == nil && keyPair != nil && keyPair.Value != nil {
		value = string(keyPair.Value)
	}

	constraintTags := strings.Split(value, ",")
	ok, failingConstraint := provider.MatchConstraints(constraintTags)
	if ok == false {
		if failingConstraint != nil {
			log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value)
		}
		return false
	}
	return true
}
<commit_msg>Fix kv<commit_after>// Package provider holds the different provider implementation.
package provider

import (
	"fmt"
	"strings"
	"text/template"
	"time"

	"errors"
	"github.com/BurntSushi/ty/fun"
	log "github.com/Sirupsen/logrus"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
	"github.com/docker/libkv"
	"github.com/docker/libkv/store"
	"github.com/emilevauge/backoff"
)

// Kv holds common configurations of key-value providers.
type Kv struct {
	BaseProvider `mapstructure:",squash"`
	Endpoint     string     `description:"Comma sepparated server endpoints"`
	Prefix       string     `description:"Prefix used for KV store"`
	TLS          *ClientTLS `description:"Enable TLS support"`
	storeType    store.Backend
	kvclient     store.Store
}

func (provider *Kv) createStore() (store.Store, error) {
	storeConfig := &store.Config{
		ConnectionTimeout: 30 * time.Second,
		Bucket:            "traefik",
	}

	if provider.TLS != nil {
		var err error
		storeConfig.TLS, err = provider.TLS.CreateTLSConfig()
		if err != nil {
			return nil, err
		}
	}
	return libkv.NewStore(
		provider.storeType,
		strings.Split(provider.Endpoint, ","),
		storeConfig,
	)
}

func (provider *Kv) watchKv(configurationChan chan<- types.ConfigMessage, prefix string, stop chan bool) error {
	operation := func() error {
		events, err := provider.kvclient.WatchTree(provider.Prefix, make(chan struct{}))
		if err != nil {
			return fmt.Errorf("Failed to KV WatchTree: %v", err)
		}
		for {
			select {
			case <-stop:
				return nil
			case _, ok := <-events:
				if !ok {
					return errors.New("watchtree channel closed")
				}
				configuration := provider.loadConfig()
				if configuration != nil {
					configurationChan <- types.ConfigMessage{
						ProviderName:  string(provider.storeType),
						Configuration: configuration,
					}
				}
			}
		}
	}

	notify := func(err error, time time.Duration) {
		log.Errorf("KV connection error: %+v, retrying in %s", err, time)
	}
	err := backoff.RetryNotify(operation, backoff.NewJobBackOff(backoff.NewExponentialBackOff()), notify)
	if err != nil {
		return fmt.Errorf("Cannot connect to KV server: %v", err)
	}
	return nil
}

func (provider *Kv) provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
	provider.Constraints = append(provider.Constraints, constraints...)
	operation := func() error {
		if _, err := provider.kvclient.Exists("qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"); err != nil {
			return fmt.Errorf("Failed to test KV store connection: %v", err)
		}
		if provider.Watch {
			pool.Go(func(stop chan bool) {
				err := provider.watchKv(configurationChan, provider.Prefix, stop)
				if err != nil {
					log.Errorf("Cannot watch KV store: %v", err)
				}
			})
		}
		configuration := provider.loadConfig()
		configurationChan <- types.ConfigMessage{
			ProviderName:  string(provider.storeType),
			Configuration: configuration,
		}
		return nil
	}
	notify := func(err error, time time.Duration) {
		log.Errorf("KV connection error: %+v, retrying in %s", err, time)
	}
	err := backoff.RetryNotify(operation, backoff.NewJobBackOff(backoff.NewExponentialBackOff()), notify)
	if err != nil {
		return fmt.Errorf("Cannot connect to KV server: %v", err)
	}
	return nil
}

func (provider *Kv) loadConfig() *types.Configuration {
	templateObjects := struct {
		Prefix string
	}{
		// Allow `/traefik/alias` to superesede `provider.Prefix`
		strings.TrimSuffix(provider.get(provider.Prefix, provider.Prefix+"/alias"), "/"),
	}

	var KvFuncMap = template.FuncMap{
		"List":        provider.list,
		"ListServers": provider.listServers,
		"Get":         provider.get,
		"SplitGet":    provider.splitGet,
		"Last":        provider.last,
	}

	configuration, err := provider.getConfiguration("templates/kv.tmpl", KvFuncMap, templateObjects)
	if err != nil {
		log.Error(err)
	}

	for key, frontend := range configuration.Frontends {
		if _, ok := configuration.Backends[frontend.Backend]; ok == false {
			delete(configuration.Frontends, key)
		}
	}

	return configuration
}

func (provider *Kv) list(keys ...string) []string {
	joinedKeys := strings.Join(keys, "")
	keysPairs, err := provider.kvclient.List(joinedKeys)
	if err != nil {
		log.Errorf("Error getting keys %s %s ", joinedKeys, err)
		return nil
	}
	directoryKeys := make(map[string]string)
	for _, key := range keysPairs {
		directory := strings.Split(strings.TrimPrefix(key.Key, joinedKeys), "/")[0]
		directoryKeys[directory] = joinedKeys + directory
	}
	return fun.Values(directoryKeys).([]string)
}

func (provider *Kv) listServers(backend string) []string {
	serverNames := provider.list(backend, "/servers/")
	return fun.Filter(func(serverName string) bool {
		return provider.checkConstraints(serverName, "/tags")
	}, serverNames).([]string)
}

func (provider *Kv) get(defaultValue string, keys ...string) string {
	joinedKeys := strings.Join(keys, "")
	keyPair, err := provider.kvclient.Get(strings.TrimPrefix(joinedKeys, "/"))
	if err != nil {
		log.Warnf("Error getting key %s %s, setting default %s", joinedKeys, err, defaultValue)
		return defaultValue
	} else if keyPair == nil {
		log.Warnf("Error getting key %s, setting default %s", joinedKeys, defaultValue)
		return defaultValue
	}
	return string(keyPair.Value)
}

func (provider *Kv) splitGet(keys ...string) []string {
	joinedKeys := strings.Join(keys, "")
	keyPair, err := provider.kvclient.Get(joinedKeys)
	if err != nil {
		log.Warnf("Error getting key %s %s, setting default empty", joinedKeys, err)
		return []string{}
	} else if keyPair == nil {
		log.Warnf("Error getting key %s, setting default %empty", joinedKeys)
		return []string{}
	}
	return strings.Split(string(keyPair.Value), ",")
}

func (provider *Kv) last(key string) string {
	splittedKey := strings.Split(key, "/")
	return splittedKey[len(splittedKey)-1]
}

func (provider *Kv) checkConstraints(keys ...string) bool {
	joinedKeys := strings.Join(keys, "")
	keyPair, err := provider.kvclient.Get(joinedKeys)

	value := ""
	if err == nil && keyPair != nil && keyPair.Value != nil {
		value = string(keyPair.Value)
	}

	constraintTags := strings.Split(value, ",")
	ok, failingConstraint := provider.MatchConstraints(constraintTags)
	if ok == false {
		if failingConstraint != nil {
			log.Debugf("Constraint %v not matching with following tags: %v", failingConstraint.String(), value)
		}
		return false
	}
	return true
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Knative Authors

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

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

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

package util

import (
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"go.uber.org/zap"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"knative.dev/pkg/apis/duck"
)

// CheckDeploymentChanged Modifies A Deployment With New Fields (If Necessary)
// Returns True If Any Modifications Were Made
func CheckDeploymentChanged(logger *zap.Logger, oldDeployment, newDeployment *appsv1.Deployment) (*appsv1.Deployment, bool) {

	// Make a copy of the old labels and annotations so we don't inadvertently
	// modify the old deployment fields directly
	updatedLabels := make(map[string]string)
	for oldKey, oldValue := range oldDeployment.ObjectMeta.Labels {
		updatedLabels[oldKey] = oldValue
	}
	updatedAnnotations := make(map[string]string)
	for oldKey, oldValue := range oldDeployment.Spec.Template.ObjectMeta.Annotations {
		updatedAnnotations[oldKey] = oldValue
	}

	metadataChanged := false
	// Add any labels in the "new" deployment to the copy of the labels from the old deployment.
	for newKey, newValue := range newDeployment.ObjectMeta.Labels {
		oldValue, ok := oldDeployment.ObjectMeta.Labels[newKey]
		if !ok || oldValue != newValue {
			metadataChanged = true
			updatedLabels[newKey] = newValue
		}
	}

	// Add any annotations in the "new" deployment to the copy of the labels from the old deployment.
	// (In particular this will trigger on differences in "kafka.eventing.knative.dev/configmap-hash")
	for newKey, newValue := range newDeployment.Spec.Template.ObjectMeta.Annotations {
		oldValue, ok := oldDeployment.Spec.Template.ObjectMeta.Annotations[newKey]
		if !ok || oldValue != newValue {
			metadataChanged = true
			updatedAnnotations[newKey] = newValue
		}
	}

	// Fields intentionally ignored:
	//    Spec.Replicas - Since a HorizontalPodAutoscaler explicitly changes this value on the deployment directly

	// Verify everything in the container spec aside from some particular exceptions (see "ignoreFields" below)
	oldContainerCount := len(oldDeployment.Spec.Template.Spec.Containers)
	if oldContainerCount == 0 {
		// This is unlikely but if it happens, replace the entire old deployment with a proper one
		logger.Warn("Old Deployment Has No Containers - Replacing Entire Deployment")
		return newDeployment, true
	}
	if len(newDeployment.Spec.Template.Spec.Containers) != 1 {
		logger.Error("New Deployment Has Incorrect Number Of Containers And Cannot Be Used")
		return oldDeployment, false
	}

	newContainer := &newDeployment.Spec.Template.Spec.Containers[0]
	oldContainer := findContainer(oldDeployment, newContainer.Name)
	if oldContainer == nil {
		logger.Error("Old Deployment Does Not Have Same Container Name - Replacing Entire Deployment")
		return newDeployment, true
	}

	ignoreFields := []cmp.Option{
		// Ignore the fields in a Container struct which are not set directly by the distributed channel reconcilers
		// and ones that are acceptable to be changed manually (such as the ImagePullPolicy)
		cmpopts.IgnoreFields(*newContainer,
			"Lifecycle",
			"TerminationMessagePolicy",
			"ImagePullPolicy",
			"SecurityContext",
			"StartupProbe",
			"TerminationMessagePath",
			"Stdin",
			"StdinOnce",
			"TTY"),
		// Ignore some other fields buried inside otherwise-relevant ones, mainly "defaults that come from empty strings,"
		// as there is no reason to restart the deployments for those changes.
		cmpopts.IgnoreFields(corev1.ContainerPort{}, "Protocol"),         // "" -> "TCP"
		cmpopts.IgnoreFields(corev1.ObjectFieldSelector{}, "APIVersion"), // "" -> "v1"
		cmpopts.IgnoreFields(corev1.HTTPGetAction{}, "Scheme"),           // "" -> "HTTP" (from inside the probes; always HTTP)
	}

	containersEqual := cmp.Equal(oldContainer, newContainer, ignoreFields...)
	if containersEqual && !metadataChanged {
		// Nothing of interest changed, so just keep the old deployment
		return oldDeployment, false
	}

	// Create an updated deployment from the old one, but using the new Container field
	updatedDeployment := oldDeployment.DeepCopy()
	if metadataChanged {
		updatedDeployment.ObjectMeta.Labels = updatedLabels
		updatedDeployment.Spec.Template.ObjectMeta.Annotations = updatedAnnotations
	}
	if !containersEqual {
		updatedDeployment.Spec.Template.Spec.Containers[0] = *newContainer
	}
	return updatedDeployment, true
}

// findContainer returns the Container with the given name in a Deployment, or nil if not found
func findContainer(deployment *appsv1.Deployment, name string) *corev1.Container {
	for _, container := range deployment.Spec.Template.Spec.Containers {
		if container.Name == name {
			return &container
		}
	}
	return nil
}

// CheckServiceChanged Modifies A Service With New Fields (If Necessary)
// Returns True If Any Modifications Were Made
func CheckServiceChanged(logger *zap.Logger, oldService, newService *corev1.Service) ([]byte, bool) {

	// Make a copy of the old labels so we don't inadvertently modify the old service fields directly
	updatedLabels := make(map[string]string)
	for oldKey, oldValue := range oldService.ObjectMeta.Labels {
		updatedLabels[oldKey] = oldValue
	}

	// Add any labels in the "new" service to the copy of the labels from the old service.
	// Annotations could be similarly updated, but there are currently no annotations being made
	// in new services anyway so it would serve no practical purpose at the moment.
	labelsChanged := false
	for newKey, newValue := range newService.ObjectMeta.Labels {
		oldValue, ok := oldService.ObjectMeta.Labels[newKey]
		if !ok || oldValue != newValue {
			labelsChanged = true
			updatedLabels[newKey] = newValue
		}
	}

	ignoreFields := []cmp.Option{
		// Ignore the fields in a Spec struct which are not set directly by the distributed channel reconcilers
		cmpopts.IgnoreFields(oldService.Spec, "ClusterIP", "Type", "SessionAffinity"),
		// Ignore some other fields buried inside otherwise-relevant ones, mainly "defaults that come from empty strings,"
		// as there is no reason to restart the deployments for those changes.
		cmpopts.IgnoreFields(corev1.ServicePort{}, "Protocol"), // "" -> "TCP"
	}

	// Verify everything in the service spec aside from some particular exceptions (see "ignoreFields" above)
	specEqual := cmp.Equal(oldService.Spec, newService.Spec, ignoreFields...)
	if specEqual && !labelsChanged {
		// Nothing of interest changed, so just keep the old service
		return nil, false
	}

	// Create an updated service from the old one, but using the new Spec field
	updatedService := oldService.DeepCopy()
	if labelsChanged {
		updatedService.ObjectMeta.Labels = updatedLabels
	}
	if !specEqual {
		updatedService.Spec = newService.Spec
	}

	// Some fields are immutable and need to be guaranteed identical before being used for patching purposes
	updatedService.Spec.ClusterIP = oldService.Spec.ClusterIP

	return createJsonPatch(logger, oldService, updatedService)
}

// createJsonPatch generates a byte array patch suitable for a Kubernetes Patch operation
// Returns false if a patch is unnecessary or impossible for the given interfaces
func createJsonPatch(logger *zap.Logger, before interface{}, after interface{}) ([]byte, bool) {
	// Create the JSON patch
	jsonPatch, err := duck.CreatePatch(before, after)
	if err != nil {
		logger.Error("Could not create service patch", zap.Error(err))
		return nil, false
	}

	if len(jsonPatch) == 0 {
		// Nothing significant changed to patch
		return nil, false
	}
	patch, err := jsonPatch.MarshalJSON()
	if err != nil {
		logger.Error("Could not marshal service patch", zap.Error(err))
		return nil, false
	}
	return patch, true
}
<commit_msg>Copy volumes as well as container when creating deployment updates (#591)<commit_after>/*
Copyright 2021 The Knative Authors

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

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

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

package util

import (
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"go.uber.org/zap"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"knative.dev/pkg/apis/duck"
)

// CheckDeploymentChanged Modifies A Deployment With New Fields (If Necessary)
// Returns True If Any Modifications Were Made
func CheckDeploymentChanged(logger *zap.Logger, oldDeployment, newDeployment *appsv1.Deployment) (*appsv1.Deployment, bool) {

	// Make a copy of the old labels and annotations so we don't inadvertently
	// modify the old deployment fields directly
	updatedLabels := make(map[string]string)
	for oldKey, oldValue := range oldDeployment.ObjectMeta.Labels {
		updatedLabels[oldKey] = oldValue
	}
	updatedAnnotations := make(map[string]string)
	for oldKey, oldValue := range oldDeployment.Spec.Template.ObjectMeta.Annotations {
		updatedAnnotations[oldKey] = oldValue
	}

	metadataChanged := false
	// Add any labels in the "new" deployment to the copy of the labels from the old deployment.
	for newKey, newValue := range newDeployment.ObjectMeta.Labels {
		oldValue, ok := oldDeployment.ObjectMeta.Labels[newKey]
		if !ok || oldValue != newValue {
			metadataChanged = true
			updatedLabels[newKey] = newValue
		}
	}

	// Add any annotations in the "new" deployment to the copy of the labels from the old deployment.
	// (In particular this will trigger on differences in "kafka.eventing.knative.dev/configmap-hash")
	for newKey, newValue := range newDeployment.Spec.Template.ObjectMeta.Annotations {
		oldValue, ok := oldDeployment.Spec.Template.ObjectMeta.Annotations[newKey]
		if !ok || oldValue != newValue {
			metadataChanged = true
			updatedAnnotations[newKey] = newValue
		}
	}

	// Fields intentionally ignored:
	//    Spec.Replicas - Since a HorizontalPodAutoscaler explicitly changes this value on the deployment directly

	// Verify everything in the container spec aside from some particular exceptions (see "ignoreFields" below)
	oldContainerCount := len(oldDeployment.Spec.Template.Spec.Containers)
	if oldContainerCount == 0 {
		// This is unlikely but if it happens, replace the entire old deployment with a proper one
		logger.Warn("Old Deployment Has No Containers - Replacing Entire Deployment")
		return newDeployment, true
	}
	if len(newDeployment.Spec.Template.Spec.Containers) != 1 {
		logger.Error("New Deployment Has Incorrect Number Of Containers And Cannot Be Used")
		return oldDeployment, false
	}

	newContainer := &newDeployment.Spec.Template.Spec.Containers[0]
	oldContainer := findContainer(oldDeployment, newContainer.Name)
	if oldContainer == nil {
		logger.Error("Old Deployment Does Not Have Same Container Name - Replacing Entire Deployment")
		return newDeployment, true
	}

	ignoreFields := []cmp.Option{
		// Ignore the fields in a Container struct which are not set directly by the distributed channel reconcilers
		// and ones that are acceptable to be changed manually (such as the ImagePullPolicy)
		cmpopts.IgnoreFields(*newContainer,
			"Lifecycle",
			"TerminationMessagePolicy",
			"ImagePullPolicy",
			"SecurityContext",
			"StartupProbe",
			"TerminationMessagePath",
			"Stdin",
			"StdinOnce",
			"TTY"),
		// Ignore some other fields buried inside otherwise-relevant ones, mainly "defaults that come from empty strings,"
		// as there is no reason to restart the deployments for those changes.
		cmpopts.IgnoreFields(corev1.ContainerPort{}, "Protocol"),         // "" -> "TCP"
		cmpopts.IgnoreFields(corev1.ObjectFieldSelector{}, "APIVersion"), // "" -> "v1"
		cmpopts.IgnoreFields(corev1.HTTPGetAction{}, "Scheme"),           // "" -> "HTTP" (from inside the probes; always HTTP)
	}

	containersEqual := cmp.Equal(oldContainer, newContainer, ignoreFields...)
	if containersEqual && !metadataChanged {
		// Nothing of interest changed, so just keep the old deployment
		return oldDeployment, false
	}

	// Create an updated deployment from the old one, but using the new Container field
	updatedDeployment := oldDeployment.DeepCopy()
	if metadataChanged {
		updatedDeployment.ObjectMeta.Labels = updatedLabels
		updatedDeployment.Spec.Template.ObjectMeta.Annotations = updatedAnnotations
	}
	if !containersEqual {
		updatedDeployment.Spec.Template.Spec.Containers[0] = *newContainer
		updatedDeployment.Spec.Template.Spec.Volumes = newDeployment.Spec.Template.Spec.Volumes
	}
	return updatedDeployment, true
}

// findContainer returns the Container with the given name in a Deployment, or nil if not found
func findContainer(deployment *appsv1.Deployment, name string) *corev1.Container {
	for _, container := range deployment.Spec.Template.Spec.Containers {
		if container.Name == name {
			return &container
		}
	}
	return nil
}

// CheckServiceChanged Modifies A Service With New Fields (If Necessary)
// Returns True If Any Modifications Were Made
func CheckServiceChanged(logger *zap.Logger, oldService, newService *corev1.Service) ([]byte, bool) {

	// Make a copy of the old labels so we don't inadvertently modify the old service fields directly
	updatedLabels := make(map[string]string)
	for oldKey, oldValue := range oldService.ObjectMeta.Labels {
		updatedLabels[oldKey] = oldValue
	}

	// Add any labels in the "new" service to the copy of the labels from the old service.
	// Annotations could be similarly updated, but there are currently no annotations being made
	// in new services anyway so it would serve no practical purpose at the moment.
	labelsChanged := false
	for newKey, newValue := range newService.ObjectMeta.Labels {
		oldValue, ok := oldService.ObjectMeta.Labels[newKey]
		if !ok || oldValue != newValue {
			labelsChanged = true
			updatedLabels[newKey] = newValue
		}
	}

	ignoreFields := []cmp.Option{
		// Ignore the fields in a Spec struct which are not set directly by the distributed channel reconcilers
		cmpopts.IgnoreFields(oldService.Spec, "ClusterIP", "Type", "SessionAffinity"),
		// Ignore some other fields buried inside otherwise-relevant ones, mainly "defaults that come from empty strings,"
		// as there is no reason to restart the deployments for those changes.
		cmpopts.IgnoreFields(corev1.ServicePort{}, "Protocol"), // "" -> "TCP"
	}

	// Verify everything in the service spec aside from some particular exceptions (see "ignoreFields" above)
	specEqual := cmp.Equal(oldService.Spec, newService.Spec, ignoreFields...)
	if specEqual && !labelsChanged {
		// Nothing of interest changed, so just keep the old service
		return nil, false
	}

	// Create an updated service from the old one, but using the new Spec field
	updatedService := oldService.DeepCopy()
	if labelsChanged {
		updatedService.ObjectMeta.Labels = updatedLabels
	}
	if !specEqual {
		updatedService.Spec = newService.Spec
	}

	// Some fields are immutable and need to be guaranteed identical before being used for patching purposes
	updatedService.Spec.ClusterIP = oldService.Spec.ClusterIP

	return createJsonPatch(logger, oldService, updatedService)
}

// createJsonPatch generates a byte array patch suitable for a Kubernetes Patch operation
// Returns false if a patch is unnecessary or impossible for the given interfaces
func createJsonPatch(logger *zap.Logger, before interface{}, after interface{}) ([]byte, bool) {
	// Create the JSON patch
	jsonPatch, err := duck.CreatePatch(before, after)
	if err != nil {
		logger.Error("Could not create service patch", zap.Error(err))
		return nil, false
	}

	if len(jsonPatch) == 0 {
		// Nothing significant changed to patch
		return nil, false
	}
	patch, err := jsonPatch.MarshalJSON()
	if err != nil {
		logger.Error("Could not marshal service patch", zap.Error(err))
		return nil, false
	}
	return patch, true
}
<|endoftext|>


<commit_before>// +build linux

package fs2

import (
	"bufio"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
)

// numToStr converts an int64 value to a string for writing to a
// cgroupv2 files with .min, .max, .low, or .high suffix.
// The value of -1 is converted to "max" for cgroupv1 compatibility
// (which used to write -1 to remove the limit).
func numToStr(value int64) (ret string) {
	switch {
	case value == 0:
		ret = ""
	case value == -1:
		ret = "max"
	default:
		ret = strconv.FormatInt(value, 10)
	}

	return ret
}

func isMemorySet(cgroup *configs.Cgroup) bool {
	return cgroup.Resources.MemoryReservation != 0 ||
		cgroup.Resources.Memory != 0 || cgroup.Resources.MemorySwap != 0
}

func setMemory(dirPath string, cgroup *configs.Cgroup) error {
	if !isMemorySet(cgroup) {
		return nil
	}
	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(cgroup.Resources.MemorySwap, cgroup.Resources.Memory)
	if err != nil {
		return err
	}
	swapStr := numToStr(swap)
	if swapStr == "" && swap == 0 && cgroup.Resources.MemorySwap > 0 {
		// memory and memorySwap set to the same value -- disable swap
		swapStr = "0"
	}
	if err := fscommon.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
		return err
	}

	if val := numToStr(cgroup.Resources.Memory); val != "" {
		if err := fscommon.WriteFile(dirPath, "memory.max", val); err != nil {
			return err
		}
	}

	// cgroup.Resources.KernelMemory is ignored

	if val := numToStr(cgroup.Resources.MemoryReservation); val != "" {
		if err := fscommon.WriteFile(dirPath, "memory.low", val); err != nil {
			return err
		}
	}

	return nil
}

func statMemory(dirPath string, stats *cgroups.Stats) error {
	// Set stats from memory.stat.
	statsFile, err := os.Open(filepath.Join(dirPath, "memory.stat"))
	if err != nil {
		return err
	}
	defer statsFile.Close()

	sc := bufio.NewScanner(statsFile)
	for sc.Scan() {
		t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text())
		if err != nil {
			return errors.Wrapf(err, "failed to parse memory.stat (%q)", sc.Text())
		}
		stats.MemoryStats.Stats[t] = v
	}
	stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]

	memoryUsage, err := getMemoryDataV2(dirPath, "")
	if err != nil {
		return err
	}
	stats.MemoryStats.Usage = memoryUsage
	swapUsage, err := getMemoryDataV2(dirPath, "swap")
	if err != nil {
		return err
	}
	stats.MemoryStats.SwapUsage = swapUsage

	stats.MemoryStats.UseHierarchy = true
	return nil
}

func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
	memoryData := cgroups.MemoryData{}

	moduleName := "memory"
	if name != "" {
		moduleName = strings.Join([]string{"memory", name}, ".")
	}
	usage := strings.Join([]string{moduleName, "current"}, ".")
	limit := strings.Join([]string{moduleName, "max"}, ".")

	value, err := fscommon.GetCgroupParamUint(path, usage)
	if err != nil {
		if moduleName != "memory" && os.IsNotExist(err) {
			return cgroups.MemoryData{}, nil
		}
		return cgroups.MemoryData{}, errors.Wrapf(err, "failed to parse %s", usage)
	}
	memoryData.Usage = value

	value, err = fscommon.GetCgroupParamUint(path, limit)
	if err != nil {
		if moduleName != "memory" && os.IsNotExist(err) {
			return cgroups.MemoryData{}, nil
		}
		return cgroups.MemoryData{}, errors.Wrapf(err, "failed to parse %s", limit)
	}
	memoryData.Limit = value

	return memoryData, nil
}
<commit_msg>never write empty string to memory.swap.max<commit_after>// +build linux

package fs2

import (
	"bufio"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/opencontainers/runc/libcontainer/cgroups"
	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/pkg/errors"
)

// numToStr converts an int64 value to a string for writing to a
// cgroupv2 files with .min, .max, .low, or .high suffix.
// The value of -1 is converted to "max" for cgroupv1 compatibility
// (which used to write -1 to remove the limit).
func numToStr(value int64) (ret string) {
	switch {
	case value == 0:
		ret = ""
	case value == -1:
		ret = "max"
	default:
		ret = strconv.FormatInt(value, 10)
	}

	return ret
}

func isMemorySet(cgroup *configs.Cgroup) bool {
	return cgroup.Resources.MemoryReservation != 0 ||
		cgroup.Resources.Memory != 0 || cgroup.Resources.MemorySwap != 0
}

func setMemory(dirPath string, cgroup *configs.Cgroup) error {
	if !isMemorySet(cgroup) {
		return nil
	}
	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(cgroup.Resources.MemorySwap, cgroup.Resources.Memory)
	if err != nil {
		return err
	}
	swapStr := numToStr(swap)
	if swapStr == "" && swap == 0 && cgroup.Resources.MemorySwap > 0 {
		// memory and memorySwap set to the same value -- disable swap
		swapStr = "0"
	}
	// never write empty string to `memory.swap.max`, it means set to 0.
	if swapStr != "" {
		if err := fscommon.WriteFile(dirPath, "memory.swap.max", swapStr); err != nil {
			return err
		}
	}

	if val := numToStr(cgroup.Resources.Memory); val != "" {
		if err := fscommon.WriteFile(dirPath, "memory.max", val); err != nil {
			return err
		}
	}

	// cgroup.Resources.KernelMemory is ignored

	if val := numToStr(cgroup.Resources.MemoryReservation); val != "" {
		if err := fscommon.WriteFile(dirPath, "memory.low", val); err != nil {
			return err
		}
	}

	return nil
}

func statMemory(dirPath string, stats *cgroups.Stats) error {
	// Set stats from memory.stat.
	statsFile, err := os.Open(filepath.Join(dirPath, "memory.stat"))
	if err != nil {
		return err
	}
	defer statsFile.Close()

	sc := bufio.NewScanner(statsFile)
	for sc.Scan() {
		t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text())
		if err != nil {
			return errors.Wrapf(err, "failed to parse memory.stat (%q)", sc.Text())
		}
		stats.MemoryStats.Stats[t] = v
	}
	stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]

	memoryUsage, err := getMemoryDataV2(dirPath, "")
	if err != nil {
		return err
	}
	stats.MemoryStats.Usage = memoryUsage
	swapUsage, err := getMemoryDataV2(dirPath, "swap")
	if err != nil {
		return err
	}
	stats.MemoryStats.SwapUsage = swapUsage

	stats.MemoryStats.UseHierarchy = true
	return nil
}

func getMemoryDataV2(path, name string) (cgroups.MemoryData, error) {
	memoryData := cgroups.MemoryData{}

	moduleName := "memory"
	if name != "" {
		moduleName = strings.Join([]string{"memory", name}, ".")
	}
	usage := strings.Join([]string{moduleName, "current"}, ".")
	limit := strings.Join([]string{moduleName, "max"}, ".")

	value, err := fscommon.GetCgroupParamUint(path, usage)
	if err != nil {
		if moduleName != "memory" && os.IsNotExist(err) {
			return cgroups.MemoryData{}, nil
		}
		return cgroups.MemoryData{}, errors.Wrapf(err, "failed to parse %s", usage)
	}
	memoryData.Usage = value

	value, err = fscommon.GetCgroupParamUint(path, limit)
	if err != nil {
		if moduleName != "memory" && os.IsNotExist(err) {
			return cgroups.MemoryData{}, nil
		}
		return cgroups.MemoryData{}, errors.Wrapf(err, "failed to parse %s", limit)
	}
	memoryData.Limit = value

	return memoryData, nil
}
<|endoftext|>