<commit_before>package bzulip

import (
	"encoding/json"
	"io/ioutil"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	gzb "github.com/matterbridge/gozulipbot"
)

type Bzulip struct {
	q       *gzb.Queue
	bot     *gzb.Bot
	streams map[int]string
	*bridge.Config
	sync.RWMutex
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bzulip{Config: cfg, streams: make(map[int]string)}
}

func (b *Bzulip) Connect() error {
	bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")}
	bot.Init()
	q, err := bot.RegisterAll()
	b.q = q
	b.bot = &bot
	if err != nil {
		b.Log.Errorf("Connect() %#v", err)
		return err
	}
	// init stream
	b.getChannel(0)
	b.Log.Info("Connection succeeded")
	go b.handleQueue()
	return nil
}

func (b *Bzulip) Disconnect() error {
	return nil
}

func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Bzulip) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)

	// Delete message
	if msg.Event == config.EventMsgDelete {
		if msg.ID == "" {
			return "", nil
		}
		_, err := b.bot.UpdateMessage(msg.ID, "")
		return "", err
	}

	// Upload a file if it exists
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.sendMessage(rmsg)
		}
		if len(msg.Extra["file"]) > 0 {
			return b.handleUploadFile(&msg)
		}
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		_, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text)
		return "", err
	}

	// Post normal message
	return b.sendMessage(msg)
}

func (b *Bzulip) getChannel(id int) string {
	if name, ok := b.streams[id]; ok {
		return name
	}
	streams, err := b.bot.GetRawStreams()
	if err != nil {
		b.Log.Errorf("getChannel: %#v", err)
		return ""
	}
	for _, stream := range streams.Streams {
		b.streams[stream.StreamID] = stream.Name
	}
	if name, ok := b.streams[id]; ok {
		return name
	}
	return ""
}

func (b *Bzulip) handleQueue() error {
	for {
		messages, err := b.q.GetEvents()
		switch err {
		case gzb.BackoffError:
			time.Sleep(time.Second * 5)
		case gzb.NoJSONError:
			b.Log.Error("Response wasn't JSON, server down or restarting? sleeping 10 seconds")
			time.Sleep(time.Second * 10)
		case gzb.BadEventQueueError:
			b.Log.Info("got a bad event queue id error, reconnecting")
			b.bot.Queues = nil
			for {
				b.q, err = b.bot.RegisterAll()
				if err != nil {
					b.Log.Errorf("reconnecting failed: %s. Sleeping 10 seconds", err)
					time.Sleep(time.Second * 10)
				}
				break
			}
		case gzb.HeartbeatError:
			b.Log.Debug("heartbeat received.")
		default:
			b.Log.Debugf("receiving error: %#v", err)
		}
		if err != nil {
			continue
		}
		for _, m := range messages {
			b.Log.Debugf("== Receiving %#v", m)
			// ignore our own messages
			if m.SenderEmail == b.GetString("login") {
				continue
			}
			rmsg := config.Message{
				Username: m.SenderFullName,
				Text:     m.Content,
				Channel:  b.getChannel(m.StreamID) + "/topic:" + m.Subject,
				Account:  b.Account,
				UserID:   strconv.Itoa(m.SenderID),
				Avatar:   m.AvatarURL,
			}
			b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
			b.Log.Debugf("<= Message is %#v", rmsg)
			b.Remote <- rmsg
			b.q.LastEventID = m.ID
		}
		time.Sleep(time.Second * 3)
	}
}

func (b *Bzulip) sendMessage(msg config.Message) (string, error) {
	topic := ""
	if strings.Contains(msg.Channel, "/topic:") {
		res := strings.Split(msg.Channel, "/topic:")
		topic = res[1]
		msg.Channel = res[0]
	}
	m := gzb.Message{
		Stream:  msg.Channel,
		Topic:   topic,
		Content: msg.Username + msg.Text,
	}
	resp, err := b.bot.Message(m)
	if err != nil {
		return "", err
	}
	if resp != nil {
		defer resp.Body.Close()
		res, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}
		var jr struct {
			ID int `json:"id"`
		}
		err = json.Unmarshal(res, &jr)
		if err != nil {
			return "", err
		}
		return strconv.Itoa(jr.ID), nil
	}
	return "", nil
}

func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) {
	for _, f := range msg.Extra["file"] {
		fi := f.(config.FileInfo)
		if fi.Comment != "" {
			msg.Text += fi.Comment + ": "
		}
		if fi.URL != "" {
			msg.Text = fi.URL
			if fi.Comment != "" {
				msg.Text = fi.Comment + ": " + fi.URL
			}
		}
		_, err := b.sendMessage(*msg)
		if err != nil {
			return "", err
		}
	}
	return "", nil
}
<commit_msg>Fix error loop (zulip) (#1210)<commit_after>package bzulip

import (
	"encoding/json"
	"io/ioutil"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/42wim/matterbridge/bridge"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	gzb "github.com/matterbridge/gozulipbot"
)

type Bzulip struct {
	q       *gzb.Queue
	bot     *gzb.Bot
	streams map[int]string
	*bridge.Config
	sync.RWMutex
}

func New(cfg *bridge.Config) bridge.Bridger {
	return &Bzulip{Config: cfg, streams: make(map[int]string)}
}

func (b *Bzulip) Connect() error {
	bot := gzb.Bot{APIKey: b.GetString("token"), APIURL: b.GetString("server") + "/api/v1/", Email: b.GetString("login")}
	bot.Init()
	q, err := bot.RegisterAll()
	b.q = q
	b.bot = &bot
	if err != nil {
		b.Log.Errorf("Connect() %#v", err)
		return err
	}
	// init stream
	b.getChannel(0)
	b.Log.Info("Connection succeeded")
	go b.handleQueue()
	return nil
}

func (b *Bzulip) Disconnect() error {
	return nil
}

func (b *Bzulip) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Bzulip) Send(msg config.Message) (string, error) {
	b.Log.Debugf("=> Receiving %#v", msg)

	// Delete message
	if msg.Event == config.EventMsgDelete {
		if msg.ID == "" {
			return "", nil
		}
		_, err := b.bot.UpdateMessage(msg.ID, "")
		return "", err
	}

	// Upload a file if it exists
	if msg.Extra != nil {
		for _, rmsg := range helper.HandleExtra(&msg, b.General) {
			b.sendMessage(rmsg)
		}
		if len(msg.Extra["file"]) > 0 {
			return b.handleUploadFile(&msg)
		}
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		_, err := b.bot.UpdateMessage(msg.ID, msg.Username+msg.Text)
		return "", err
	}

	// Post normal message
	return b.sendMessage(msg)
}

func (b *Bzulip) getChannel(id int) string {
	if name, ok := b.streams[id]; ok {
		return name
	}
	streams, err := b.bot.GetRawStreams()
	if err != nil {
		b.Log.Errorf("getChannel: %#v", err)
		return ""
	}
	for _, stream := range streams.Streams {
		b.streams[stream.StreamID] = stream.Name
	}
	if name, ok := b.streams[id]; ok {
		return name
	}
	return ""
}

func (b *Bzulip) handleQueue() error {
	for {
		messages, err := b.q.GetEvents()
		switch err {
		case gzb.BackoffError:
			time.Sleep(time.Second * 5)
		case gzb.NoJSONError:
			b.Log.Error("Response wasn't JSON, server down or restarting? sleeping 10 seconds")
			time.Sleep(time.Second * 10)
		case gzb.BadEventQueueError:
			b.Log.Info("got a bad event queue id error, reconnecting")
			b.bot.Queues = nil
			for {
				b.q, err = b.bot.RegisterAll()
				if err != nil {
					b.Log.Errorf("reconnecting failed: %s. Sleeping 10 seconds", err)
					time.Sleep(time.Second * 10)
				}
				break
			}
		case gzb.HeartbeatError:
			b.Log.Debug("heartbeat received.")
		default:
			b.Log.Debugf("receiving error: %#v", err)
		}
		if err != nil {
			continue
		}
		for _, m := range messages {
			b.Log.Debugf("== Receiving %#v", m)
			// ignore our own messages
			if m.SenderEmail == b.GetString("login") {
				continue
			}
			rmsg := config.Message{
				Username: m.SenderFullName,
				Text:     m.Content,
				Channel:  b.getChannel(m.StreamID) + "/topic:" + m.Subject,
				Account:  b.Account,
				UserID:   strconv.Itoa(m.SenderID),
				Avatar:   m.AvatarURL,
			}
			b.Log.Debugf("<= Sending message from %s on %s to gateway", rmsg.Username, b.Account)
			b.Log.Debugf("<= Message is %#v", rmsg)
			b.Remote <- rmsg
		}

		time.Sleep(time.Second * 3)
	}
}

func (b *Bzulip) sendMessage(msg config.Message) (string, error) {
	topic := ""
	if strings.Contains(msg.Channel, "/topic:") {
		res := strings.Split(msg.Channel, "/topic:")
		topic = res[1]
		msg.Channel = res[0]
	}
	m := gzb.Message{
		Stream:  msg.Channel,
		Topic:   topic,
		Content: msg.Username + msg.Text,
	}
	resp, err := b.bot.Message(m)
	if err != nil {
		return "", err
	}
	if resp != nil {
		defer resp.Body.Close()
		res, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", err
		}
		var jr struct {
			ID int `json:"id"`
		}
		err = json.Unmarshal(res, &jr)
		if err != nil {
			return "", err
		}
		return strconv.Itoa(jr.ID), nil
	}
	return "", nil
}

func (b *Bzulip) handleUploadFile(msg *config.Message) (string, error) {
	for _, f := range msg.Extra["file"] {
		fi := f.(config.FileInfo)
		if fi.Comment != "" {
			msg.Text += fi.Comment + ": "
		}
		if fi.URL != "" {
			msg.Text = fi.URL
			if fi.Comment != "" {
				msg.Text = fi.Comment + ": " + fi.URL
			}
		}
		_, err := b.sendMessage(*msg)
		if err != nil {
			return "", err
		}
	}
	return "", nil
}
<|endoftext|>


<commit_before>package process_tracker

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path"
	"sync"
	"syscall"

	"github.com/cloudfoundry-incubator/garden/warden"
	"github.com/cloudfoundry/gunk/command_runner"
)

type Process struct {
	ID uint32

	containerPath string
	runner        command_runner.CommandRunner

	waitingLinks   *sync.Mutex
	completionLock *sync.Mutex
	runningLink    *sync.Once
	link           *exec.Cmd

	linked   chan struct{}
	unlinked <-chan struct{}

	streams     []chan warden.ProcessStream
	streamsLock *sync.RWMutex

	completed bool

	exitStatus uint32
	stdout     *namedStream
	stderr     *namedStream
}

func NewProcess(
	id uint32,
	containerPath string,
	runner command_runner.CommandRunner,
) *Process {
	unlinked := make(chan struct{}, 1)
	unlinked <- struct{}{}

	p := &Process{
		ID: id,

		containerPath: containerPath,
		runner:        runner,

		streamsLock: &sync.RWMutex{},

		waitingLinks:   &sync.Mutex{},
		runningLink:    &sync.Once{},
		completionLock: &sync.Mutex{},
		linked:         make(chan struct{}),
		unlinked:       unlinked,
	}

	p.stdout = newNamedStream(p, warden.ProcessStreamSourceStdout)
	p.stderr = newNamedStream(p, warden.ProcessStreamSourceStderr)

	return p
}

func (p *Process) Spawn(cmd *exec.Cmd) (ready, active chan error) {
	ready = make(chan error, 1)
	active = make(chan error, 1)

	spawnPath := path.Join(p.containerPath, "bin", "iomux-spawn")
	processDir := path.Join(p.containerPath, "processes", fmt.Sprintf("%d", p.ID))

	mkdir := &exec.Cmd{
		Path: "mkdir",
		Args: []string{"-p", processDir},
	}

	err := p.runner.Run(mkdir)
	if err != nil {
		ready <- err
		return
	}

	spawn := &exec.Cmd{
		Path:  "bash",
		Stdin: cmd.Stdin,
	}

	spawn.Args = append([]string{"-c", "cat | " + spawnPath + ` "$@" &`, spawnPath, processDir}, cmd.Path)
	spawn.Args = append(spawn.Args, cmd.Args...)

	spawn.Env = cmd.Env

	spawnR, spawnW, err := os.Pipe()
	if err != nil {
		ready <- err
		return
	}

	spawn.Stdout = spawnW

	spawnOut := bufio.NewReader(spawnR)

	err = p.runner.Background(spawn)
	if err != nil {
		ready <- err
		return
	}

	go spawn.Wait()

	go func() {
		defer func() {
			spawnW.Close()
			spawnR.Close()
		}()

		_, err := spawnOut.ReadBytes('\n')
		if err != nil {
			ready <- err
			return
		}

		ready <- nil

		_, err = spawnOut.ReadBytes('\n')
		if err != nil {
			active <- err
			return
		}

		active <- nil
	}()

	return
}

func (p *Process) Link() {
	p.waitingLinks.Lock()
	defer p.waitingLinks.Unlock()

	p.runningLink.Do(p.runLinker)
}

func (p *Process) Unlink() error {
	<-p.linked

	select {
	case <-p.unlinked:
	default:
		// link already exited
		return nil
	}

	return p.runner.Signal(p.link, os.Interrupt)
}

func (p *Process) Stream() chan warden.ProcessStream {
	return p.registerStream()
}

func (p *Process) runLinker() {
	linkPath := path.Join(p.containerPath, "bin", "iomux-link")
	processDir := path.Join(p.containerPath, "processes", fmt.Sprintf("%d", p.ID))

	p.link = &exec.Cmd{
		Path:   linkPath,
		Args:   []string{"-w", path.Join(processDir, "cursors"), processDir},
		Stdout: p.stdout,
		Stderr: p.stderr,
	}

	p.runner.Start(p.link)

	close(p.linked)

	p.runner.Wait(p.link)

	// if the process is explicitly .Unlinked, block forever; the fact that
	// iomux-link exited should not bubble up to the caller as the linked
	// process didn't actually exit.
	//
	// this is done by .Unlink reading the single value off of .unlinked before
	// interrupting iomux-link, so this read should either block forever in this
	// case or read the value off if the process exited naturally.
	//
	// if .Unlink is called and the value is pulled off, it then knows to not
	// try to terminate the iomux-link, as this only happens if it already
	// exited
	<-p.unlinked

	exitStatus := uint32(255)

	if p.link.ProcessState != nil {
		exitStatus = uint32(p.link.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
	}

	p.exitStatus = exitStatus

	p.closeStreams()
}

func (p *Process) registerStream() chan warden.ProcessStream {
	p.streamsLock.Lock()
	defer p.streamsLock.Unlock()

	stream := make(chan warden.ProcessStream, 1000)

	p.streams = append(p.streams, stream)

	if p.completed {
		defer p.closeStreams()
	}

	return stream
}

func (p *Process) sendToStreams(chunk warden.ProcessStream) {
	p.streamsLock.RLock()
	defer p.streamsLock.RUnlock()

	for _, stream := range p.streams {
		select {
		case stream <- chunk:
		default:
		}
	}
}

func (p *Process) closeStreams() {
	p.streamsLock.RLock()
	defer p.streamsLock.RUnlock()

	for _, stream := range p.streams {
		stream <- warden.ProcessStream{ExitStatus: &(p.exitStatus)}
		close(stream)
	}

	p.streams = nil
	p.completed = true
}
<commit_msg>fix hanging in edge case when spawning iomux-spawn<commit_after>package process_tracker

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path"
	"sync"
	"syscall"

	"github.com/cloudfoundry-incubator/garden/warden"
	"github.com/cloudfoundry/gunk/command_runner"
)

type Process struct {
	ID uint32

	containerPath string
	runner        command_runner.CommandRunner

	waitingLinks   *sync.Mutex
	completionLock *sync.Mutex
	runningLink    *sync.Once
	link           *exec.Cmd

	linked   chan struct{}
	unlinked <-chan struct{}

	streams     []chan warden.ProcessStream
	streamsLock *sync.RWMutex

	completed bool

	exitStatus uint32
	stdout     *namedStream
	stderr     *namedStream
}

func NewProcess(
	id uint32,
	containerPath string,
	runner command_runner.CommandRunner,
) *Process {
	unlinked := make(chan struct{}, 1)
	unlinked <- struct{}{}

	p := &Process{
		ID: id,

		containerPath: containerPath,
		runner:        runner,

		streamsLock: &sync.RWMutex{},

		waitingLinks:   &sync.Mutex{},
		runningLink:    &sync.Once{},
		completionLock: &sync.Mutex{},
		linked:         make(chan struct{}),
		unlinked:       unlinked,
	}

	p.stdout = newNamedStream(p, warden.ProcessStreamSourceStdout)
	p.stderr = newNamedStream(p, warden.ProcessStreamSourceStderr)

	return p
}

func (p *Process) Spawn(cmd *exec.Cmd) (ready, active chan error) {
	ready = make(chan error, 1)
	active = make(chan error, 1)

	spawnPath := path.Join(p.containerPath, "bin", "iomux-spawn")
	processDir := path.Join(p.containerPath, "processes", fmt.Sprintf("%d", p.ID))

	mkdir := &exec.Cmd{
		Path: "mkdir",
		Args: []string{"-p", processDir},
	}

	err := p.runner.Run(mkdir)
	if err != nil {
		ready <- err
		return
	}

	spawn := &exec.Cmd{
		Path:  "bash",
		Stdin: cmd.Stdin,
	}

	spawn.Args = append([]string{"-c", "cat | " + spawnPath + ` "$@" &`, spawnPath, processDir}, cmd.Path)
	spawn.Args = append(spawn.Args, cmd.Args...)

	spawn.Env = cmd.Env

	spawnR, err := spawn.StdoutPipe()
	if err != nil {
		ready <- err
		return
	}

	spawnOut := bufio.NewReader(spawnR)

	err = p.runner.Background(spawn)
	if err != nil {
		ready <- err
		return
	}

	go func() {
		_, err := spawnOut.ReadBytes('\n')
		if err != nil {
			ready <- err
			return
		}

		ready <- nil

		_, err = spawnOut.ReadBytes('\n')
		if err != nil {
			active <- err
			return
		}

		active <- nil

		spawn.Wait()
	}()

	return
}

func (p *Process) Link() {
	p.waitingLinks.Lock()
	defer p.waitingLinks.Unlock()

	p.runningLink.Do(p.runLinker)
}

func (p *Process) Unlink() error {
	<-p.linked

	select {
	case <-p.unlinked:
	default:
		// link already exited
		return nil
	}

	return p.runner.Signal(p.link, os.Interrupt)
}

func (p *Process) Stream() chan warden.ProcessStream {
	return p.registerStream()
}

func (p *Process) runLinker() {
	linkPath := path.Join(p.containerPath, "bin", "iomux-link")
	processDir := path.Join(p.containerPath, "processes", fmt.Sprintf("%d", p.ID))

	p.link = &exec.Cmd{
		Path:   linkPath,
		Args:   []string{"-w", path.Join(processDir, "cursors"), processDir},
		Stdout: p.stdout,
		Stderr: p.stderr,
	}

	p.runner.Start(p.link)

	close(p.linked)

	p.runner.Wait(p.link)

	// if the process is explicitly .Unlinked, block forever; the fact that
	// iomux-link exited should not bubble up to the caller as the linked
	// process didn't actually exit.
	//
	// this is done by .Unlink reading the single value off of .unlinked before
	// interrupting iomux-link, so this read should either block forever in this
	// case or read the value off if the process exited naturally.
	//
	// if .Unlink is called and the value is pulled off, it then knows to not
	// try to terminate the iomux-link, as this only happens if it already
	// exited
	<-p.unlinked

	exitStatus := uint32(255)

	if p.link.ProcessState != nil {
		exitStatus = uint32(p.link.ProcessState.Sys().(syscall.WaitStatus).ExitStatus())
	}

	p.exitStatus = exitStatus

	p.closeStreams()
}

func (p *Process) registerStream() chan warden.ProcessStream {
	p.streamsLock.Lock()
	defer p.streamsLock.Unlock()

	stream := make(chan warden.ProcessStream, 1000)

	p.streams = append(p.streams, stream)

	if p.completed {
		defer p.closeStreams()
	}

	return stream
}

func (p *Process) sendToStreams(chunk warden.ProcessStream) {
	p.streamsLock.RLock()
	defer p.streamsLock.RUnlock()

	for _, stream := range p.streams {
		select {
		case stream <- chunk:
		default:
		}
	}
}

func (p *Process) closeStreams() {
	p.streamsLock.RLock()
	defer p.streamsLock.RUnlock()

	for _, stream := range p.streams {
		stream <- warden.ProcessStream{ExitStatus: &(p.exitStatus)}
		close(stream)
	}

	p.streams = nil
	p.completed = true
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 restclient

import (
	"fmt"
	"net/url"
	"path"

	"k8s.io/kubernetes/pkg/api/unversioned"
)

// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
// to use with a Client at a given API version following the standard conventions for a
// Kubernetes API.
func DefaultServerURL(host, apiPath string, groupVersion unversioned.GroupVersion, defaultTLS bool) (*url.URL, string, error) {
	if host == "" {
		return nil, "", fmt.Errorf("host must be a URL or a host:port pair")
	}
	base := host
	hostURL, err := url.Parse(base)
	if err != nil {
		return nil, "", err
	}
	if hostURL.Scheme == "" {
		scheme := "http://"
		if defaultTLS {
			scheme = "https://"
		}
		hostURL, err = url.Parse(scheme + base)
		if err != nil {
			return nil, "", err
		}
		if hostURL.Path != "" && hostURL.Path != "/" {
			return nil, "", fmt.Errorf("host must be a URL or a host:port pair: %q", base)
		}
	}

	// hostURL.Path is optional; a non-empty Path is treated as a prefix that is to be applied to
	// all URIs used to access the host. this is useful when there's a proxy in front of the
	// apiserver that has relocated the apiserver endpoints, forwarding all requests from, for
	// example, /a/b/c to the apiserver. in this case the Path should be /a/b/c.
	//
	// if running without a frontend proxy (that changes the location of the apiserver), then
	// hostURL.Path should be blank.
	//
	// versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base
	versionedAPIPath := path.Join("/", apiPath)

	// Add the version to the end of the path
	if len(groupVersion.Group) > 0 {
		versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Group, groupVersion.Version)

	} else {
		versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version)

	}

	return hostURL, versionedAPIPath, nil
}

// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
// requires Host and Version to be set prior to being called.
func defaultServerUrlFor(config *Config) (*url.URL, string, error) {
	// TODO: move the default to secure when the apiserver supports TLS by default
	// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
	hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
	hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
	defaultTLS := hasCA || hasCert || config.Insecure
	host := config.Host
	if host == "" {
		host = "localhost"
	}

	if config.GroupVersion != nil {
		return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS)
	}
	return DefaultServerURL(host, config.APIPath, unversioned.GroupVersion{}, defaultTLS)
}
<commit_msg>Fix problem specifying fqdn:port in command line<commit_after>/*
Copyright 2016 The Kubernetes 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 restclient

import (
	"fmt"
	"net/url"
	"path"

	"k8s.io/kubernetes/pkg/api/unversioned"
)

// DefaultServerURL converts a host, host:port, or URL string to the default base server API path
// to use with a Client at a given API version following the standard conventions for a
// Kubernetes API.
func DefaultServerURL(host, apiPath string, groupVersion unversioned.GroupVersion, defaultTLS bool) (*url.URL, string, error) {
	if host == "" {
		return nil, "", fmt.Errorf("host must be a URL or a host:port pair")
	}
	base := host
	hostURL, err := url.Parse(base)
	if err != nil {
		return nil, "", err
	}
	if hostURL.Scheme == "" || hostURL.Host == "" {
		scheme := "http://"
		if defaultTLS {
			scheme = "https://"
		}
		hostURL, err = url.Parse(scheme + base)
		if err != nil {
			return nil, "", err
		}
		if hostURL.Path != "" && hostURL.Path != "/" {
			return nil, "", fmt.Errorf("host must be a URL or a host:port pair: %q", base)
		}
	}

	// hostURL.Path is optional; a non-empty Path is treated as a prefix that is to be applied to
	// all URIs used to access the host. this is useful when there's a proxy in front of the
	// apiserver that has relocated the apiserver endpoints, forwarding all requests from, for
	// example, /a/b/c to the apiserver. in this case the Path should be /a/b/c.
	//
	// if running without a frontend proxy (that changes the location of the apiserver), then
	// hostURL.Path should be blank.
	//
	// versionedAPIPath, a path relative to baseURL.Path, points to a versioned API base
	versionedAPIPath := path.Join("/", apiPath)

	// Add the version to the end of the path
	if len(groupVersion.Group) > 0 {
		versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Group, groupVersion.Version)

	} else {
		versionedAPIPath = path.Join(versionedAPIPath, groupVersion.Version)

	}

	return hostURL, versionedAPIPath, nil
}

// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
// requires Host and Version to be set prior to being called.
func defaultServerUrlFor(config *Config) (*url.URL, string, error) {
	// TODO: move the default to secure when the apiserver supports TLS by default
	// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
	hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
	hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
	defaultTLS := hasCA || hasCert || config.Insecure
	host := config.Host
	if host == "" {
		host = "localhost"
	}

	if config.GroupVersion != nil {
		return DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS)
	}
	return DefaultServerURL(host, config.APIPath, unversioned.GroupVersion{}, defaultTLS)
}
<|endoftext|>


<commit_before>package deployment

import (
	"git.containerum.net/ch/kube-client/pkg/model"
	"github.com/containerum/chkit/pkg/model/container"
)

type Deployment struct {
	Name       string
	Replicas   uint
	Status     *Status
	Containers []container.Container
	origin     model.Deployment
}

func DeploymentFromKube(kubeDeployment model.Deployment) Deployment {
	var status *Status
	if kubeDeployment.Status != nil {
		st := StatusFromKubeStatus(*kubeDeployment.Status)
		status = &st
	}
	containers := make([]container.Container, 0, len(kubeDeployment.Containers))
	for _, kubeContainer := range kubeDeployment.Containers {
		containers = append(containers, container.Container{kubeContainer})
	}
	return Deployment{
		Name:       kubeDeployment.Name,
		Replicas:   uint(kubeDeployment.Replicas),
		Status:     status,
		Containers: containers,
		origin:     kubeDeployment,
	}
}
<commit_msg>add struct key<commit_after>package deployment

import (
	"git.containerum.net/ch/kube-client/pkg/model"
	"github.com/containerum/chkit/pkg/model/container"
)

type Deployment struct {
	Name       string
	Replicas   uint
	Status     *Status
	Containers []container.Container
	origin     model.Deployment
}

func DeploymentFromKube(kubeDeployment model.Deployment) Deployment {
	var status *Status
	if kubeDeployment.Status != nil {
		st := StatusFromKubeStatus(*kubeDeployment.Status)
		status = &st
	}
	containers := make([]container.Container, 0, len(kubeDeployment.Containers))
	for _, kubeContainer := range kubeDeployment.Containers {
		containers = append(containers, container.Container{Container: kubeContainer})
	}
	return Deployment{
		Name:       kubeDeployment.Name,
		Replicas:   uint(kubeDeployment.Replicas),
		Status:     status,
		Containers: containers,
		origin:     kubeDeployment,
	}
}
<|endoftext|>


<commit_before>/*
Copyright The Helm 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 releaseutil

import (
	"log"
	"path"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"sigs.k8s.io/yaml"

	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/release"
)

// Manifest represents a manifest file, which has a name and some content.
type Manifest struct {
	Name    string
	Content string
	Head    *SimpleHead
}

// manifestFile represents a file that contains a manifest.
type manifestFile struct {
	entries map[string]string
	path    string
	apis    chartutil.VersionSet
}

// result is an intermediate structure used during sorting.
type result struct {
	hooks   []*release.Hook
	generic []Manifest
}

// TODO: Refactor this out. It's here because naming conventions were not followed through.
// So fix the Test hook names and then remove this.
var events = map[string]release.HookEvent{
	release.HookPreInstall.String():   release.HookPreInstall,
	release.HookPostInstall.String():  release.HookPostInstall,
	release.HookPreDelete.String():    release.HookPreDelete,
	release.HookPostDelete.String():   release.HookPostDelete,
	release.HookPreUpgrade.String():   release.HookPreUpgrade,
	release.HookPostUpgrade.String():  release.HookPostUpgrade,
	release.HookPreRollback.String():  release.HookPreRollback,
	release.HookPostRollback.String(): release.HookPostRollback,
	release.HookTest.String():         release.HookTest,
	// Support test-success for backward compatibility with Helm 2 tests
	"test-success": release.HookTest,
}

// SortManifests takes a map of filename/YAML contents, splits the file
// by manifest entries, and sorts the entries into hook types.
//
// The resulting hooks struct will be populated with all of the generated hooks.
// Any file that does not declare one of the hook types will be placed in the
// 'generic' bucket.
//
// Files that do not parse into the expected format are simply placed into a map and
// returned.
func SortManifests(files map[string]string, apis chartutil.VersionSet, sort KindSortOrder) ([]*release.Hook, []Manifest, error) {
	result := &result{}

	for filePath, c := range files {

		// Skip partials. We could return these as a separate map, but there doesn't
		// seem to be any need for that at this time.
		if strings.HasPrefix(path.Base(filePath), "_") {
			continue
		}
		// Skip empty files and log this.
		if strings.TrimSpace(c) == "" {
			continue
		}

		manifestFile := &manifestFile{
			entries: SplitManifests(c),
			path:    filePath,
			apis:    apis,
		}

		if err := manifestFile.sort(result); err != nil {
			return result.hooks, result.generic, err
		}
	}

	return result.hooks, sortByKind(result.generic, sort), nil
}

// sort takes a manifestFile object which may contain multiple resource definition
// entries and sorts each entry by hook types, and saves the resulting hooks and
// generic manifests (or non-hooks) to the result struct.
//
// To determine hook type, it looks for a YAML structure like this:
//
//  kind: SomeKind
//  apiVersion: v1
// 	metadata:
//		annotations:
//			helm.sh/hook: pre-install
//
// To determine the policy to delete the hook, it looks for a YAML structure like this:
//
//  kind: SomeKind
//  apiVersion: v1
//  metadata:
// 		annotations:
// 			helm.sh/hook-delete-policy: hook-succeeded
func (file *manifestFile) sort(result *result) error {
	for _, m := range file.entries {
		var entry SimpleHead
		if err := yaml.Unmarshal([]byte(m), &entry); err != nil {
			return errors.Wrapf(err, "YAML parse error on %s", file.path)
		}

		if entry.Version != "" && !file.apis.Has(entry.Version) {
			return errors.Errorf("apiVersion %q in %s is not available", entry.Version, file.path)
		}

		if !hasAnyAnnotation(entry) {
			result.generic = append(result.generic, Manifest{
				Name:    file.path,
				Content: m,
				Head:    &entry,
			})
			continue
		}

		hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation]
		if !ok {
			result.generic = append(result.generic, Manifest{
				Name:    file.path,
				Content: m,
				Head:    &entry,
			})
			continue
		}

		hw := calculateHookWeight(entry)

		h := &release.Hook{
			Name:           entry.Metadata.Name,
			Kind:           entry.Kind,
			Path:           file.path,
			Manifest:       m,
			Events:         []release.HookEvent{},
			Weight:         hw,
			DeletePolicies: []release.HookDeletePolicy{},
		}

		isUnknownHook := false
		for _, hookType := range strings.Split(hookTypes, ",") {
			hookType = strings.ToLower(strings.TrimSpace(hookType))
			e, ok := events[hookType]
			if !ok {
				isUnknownHook = true
				break
			}
			h.Events = append(h.Events, e)
		}

		if isUnknownHook {
			log.Printf("info: skipping unknown hook: %q", hookTypes)
			continue
		}

		result.hooks = append(result.hooks, h)

		operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) {
			h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value))
		})
	}

	return nil
}

// hasAnyAnnotation returns true if the given entry has any annotations at all.
func hasAnyAnnotation(entry SimpleHead) bool {
	return entry.Metadata != nil &&
		entry.Metadata.Annotations != nil &&
		len(entry.Metadata.Annotations) != 0
}

// calculateHookWeight finds the weight in the hook weight annotation.
//
// If no weight is found, the assigned weight is 0
func calculateHookWeight(entry SimpleHead) int {
	hws := entry.Metadata.Annotations[release.HookWeightAnnotation]
	hw, err := strconv.Atoi(hws)
	if err != nil {
		hw = 0
	}
	return hw
}

// operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation
func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) {
	if dps, ok := entry.Metadata.Annotations[annotation]; ok {
		for _, dp := range strings.Split(dps, ",") {
			dp = strings.ToLower(strings.TrimSpace(dp))
			operate(dp)
		}
	}
}
<commit_msg>fix(releaseutil): Removes API version checks from kind sorter<commit_after>/*
Copyright The Helm 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 releaseutil

import (
	"log"
	"path"
	"strconv"
	"strings"

	"github.com/pkg/errors"
	"sigs.k8s.io/yaml"

	"helm.sh/helm/v3/pkg/chartutil"
	"helm.sh/helm/v3/pkg/release"
)

// Manifest represents a manifest file, which has a name and some content.
type Manifest struct {
	Name    string
	Content string
	Head    *SimpleHead
}

// manifestFile represents a file that contains a manifest.
type manifestFile struct {
	entries map[string]string
	path    string
	apis    chartutil.VersionSet
}

// result is an intermediate structure used during sorting.
type result struct {
	hooks   []*release.Hook
	generic []Manifest
}

// TODO: Refactor this out. It's here because naming conventions were not followed through.
// So fix the Test hook names and then remove this.
var events = map[string]release.HookEvent{
	release.HookPreInstall.String():   release.HookPreInstall,
	release.HookPostInstall.String():  release.HookPostInstall,
	release.HookPreDelete.String():    release.HookPreDelete,
	release.HookPostDelete.String():   release.HookPostDelete,
	release.HookPreUpgrade.String():   release.HookPreUpgrade,
	release.HookPostUpgrade.String():  release.HookPostUpgrade,
	release.HookPreRollback.String():  release.HookPreRollback,
	release.HookPostRollback.String(): release.HookPostRollback,
	release.HookTest.String():         release.HookTest,
	// Support test-success for backward compatibility with Helm 2 tests
	"test-success": release.HookTest,
}

// SortManifests takes a map of filename/YAML contents, splits the file
// by manifest entries, and sorts the entries into hook types.
//
// The resulting hooks struct will be populated with all of the generated hooks.
// Any file that does not declare one of the hook types will be placed in the
// 'generic' bucket.
//
// Files that do not parse into the expected format are simply placed into a map and
// returned.
func SortManifests(files map[string]string, apis chartutil.VersionSet, sort KindSortOrder) ([]*release.Hook, []Manifest, error) {
	result := &result{}

	for filePath, c := range files {

		// Skip partials. We could return these as a separate map, but there doesn't
		// seem to be any need for that at this time.
		if strings.HasPrefix(path.Base(filePath), "_") {
			continue
		}
		// Skip empty files and log this.
		if strings.TrimSpace(c) == "" {
			continue
		}

		manifestFile := &manifestFile{
			entries: SplitManifests(c),
			path:    filePath,
			apis:    apis,
		}

		if err := manifestFile.sort(result); err != nil {
			return result.hooks, result.generic, err
		}
	}

	return result.hooks, sortByKind(result.generic, sort), nil
}

// sort takes a manifestFile object which may contain multiple resource definition
// entries and sorts each entry by hook types, and saves the resulting hooks and
// generic manifests (or non-hooks) to the result struct.
//
// To determine hook type, it looks for a YAML structure like this:
//
//  kind: SomeKind
//  apiVersion: v1
// 	metadata:
//		annotations:
//			helm.sh/hook: pre-install
//
// To determine the policy to delete the hook, it looks for a YAML structure like this:
//
//  kind: SomeKind
//  apiVersion: v1
//  metadata:
// 		annotations:
// 			helm.sh/hook-delete-policy: hook-succeeded
func (file *manifestFile) sort(result *result) error {
	for _, m := range file.entries {
		var entry SimpleHead
		if err := yaml.Unmarshal([]byte(m), &entry); err != nil {
			return errors.Wrapf(err, "YAML parse error on %s", file.path)
		}

		if !hasAnyAnnotation(entry) {
			result.generic = append(result.generic, Manifest{
				Name:    file.path,
				Content: m,
				Head:    &entry,
			})
			continue
		}

		hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation]
		if !ok {
			result.generic = append(result.generic, Manifest{
				Name:    file.path,
				Content: m,
				Head:    &entry,
			})
			continue
		}

		hw := calculateHookWeight(entry)

		h := &release.Hook{
			Name:           entry.Metadata.Name,
			Kind:           entry.Kind,
			Path:           file.path,
			Manifest:       m,
			Events:         []release.HookEvent{},
			Weight:         hw,
			DeletePolicies: []release.HookDeletePolicy{},
		}

		isUnknownHook := false
		for _, hookType := range strings.Split(hookTypes, ",") {
			hookType = strings.ToLower(strings.TrimSpace(hookType))
			e, ok := events[hookType]
			if !ok {
				isUnknownHook = true
				break
			}
			h.Events = append(h.Events, e)
		}

		if isUnknownHook {
			log.Printf("info: skipping unknown hook: %q", hookTypes)
			continue
		}

		result.hooks = append(result.hooks, h)

		operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) {
			h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value))
		})
	}

	return nil
}

// hasAnyAnnotation returns true if the given entry has any annotations at all.
func hasAnyAnnotation(entry SimpleHead) bool {
	return entry.Metadata != nil &&
		entry.Metadata.Annotations != nil &&
		len(entry.Metadata.Annotations) != 0
}

// calculateHookWeight finds the weight in the hook weight annotation.
//
// If no weight is found, the assigned weight is 0
func calculateHookWeight(entry SimpleHead) int {
	hws := entry.Metadata.Annotations[release.HookWeightAnnotation]
	hw, err := strconv.Atoi(hws)
	if err != nil {
		hw = 0
	}
	return hw
}

// operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation
func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) {
	if dps, ok := entry.Metadata.Annotations[annotation]; ok {
		for _, dp := range strings.Split(dps, ",") {
			dp = strings.ToLower(strings.TrimSpace(dp))
			operate(dp)
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold 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 cache

import (
	"context"
	"fmt"
	"io/ioutil"
	"sync"

	"github.com/sirupsen/logrus"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
)

func (c *cache) lookupArtifacts(ctx context.Context, tags tag.ImageTags, artifacts []*latest.Artifact) []cacheDetails {
	details := make([]cacheDetails, len(artifacts))
	// Create a new `artifactHasher` on every new dev loop.
	// This way every artifact hash is calculated at most once in a single dev loop, and recalculated on every dev loop.
	h := newArtifactHasherFunc(c.artifactGraph, c.lister, c.cfg.Mode())
	var wg sync.WaitGroup
	for i := range artifacts {
		wg.Add(1)

		i := i
		go func() {
			details[i] = c.lookup(ctx, artifacts[i], tags[artifacts[i].ImageName], h)
			wg.Done()
		}()
	}
	wg.Wait()

	return details
}

func (c *cache) lookup(ctx context.Context, a *latest.Artifact, tag string, h artifactHasher) cacheDetails {
	hash, err := h.hash(ctx, a)
	if err != nil {
		return failed{err: fmt.Errorf("getting hash for artifact %q: %s", a.ImageName, err)}
	}

	c.cacheMutex.RLock()
	entry, cacheHit := c.artifactCache[hash]
	c.cacheMutex.RUnlock()
	if !cacheHit {
		if entry, err = c.tryImport(ctx, a, tag, hash); err != nil {
			logrus.Debugf("Could not import artifact from Docker, building instead (%s)", err)
			return needsBuilding{hash: hash}
		}
	}

	if c.imagesAreLocal {
		return c.lookupLocal(ctx, hash, tag, entry)
	}
	return c.lookupRemote(ctx, hash, tag, entry)
}

func (c *cache) lookupLocal(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
	if entry.ID == "" {
		return needsBuilding{hash: hash}
	}

	// Check the imageID for the tag
	idForTag, err := c.client.ImageID(ctx, tag)
	if err != nil {
		// Rely on actionable errors thrown from pkg/skaffold/docker.LocalDaemon api.
		return failed{err: err}
	}

	// Image exists locally with the same tag
	if idForTag == entry.ID {
		return found{hash: hash}
	}

	// Image exists locally with a different tag
	if c.client.ImageExists(ctx, entry.ID) {
		return needsLocalTagging{hash: hash, tag: tag, imageID: entry.ID}
	}

	return needsBuilding{hash: hash}
}

func (c *cache) lookupRemote(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
	if remoteDigest, err := docker.RemoteDigest(tag, c.cfg); err == nil {
		// Image exists remotely with the same tag and digest
		if remoteDigest == entry.Digest {
			return found{hash: hash}
		}
	}

	// Image exists remotely with a different tag
	fqn := tag + "@" + entry.Digest // Actual tag will be ignored but we need the registry and the digest part of it.
	if remoteDigest, err := docker.RemoteDigest(fqn, c.cfg); err == nil {
		if remoteDigest == entry.Digest {
			return needsRemoteTagging{hash: hash, tag: tag, digest: entry.Digest}
		}
	}

	// Image exists locally
	if entry.ID != "" && c.client != nil && c.client.ImageExists(ctx, entry.ID) {
		return needsPushing{hash: hash, tag: tag, imageID: entry.ID}
	}

	return needsBuilding{hash: hash}
}

func (c *cache) tryImport(ctx context.Context, a *latest.Artifact, tag string, hash string) (ImageDetails, error) {
	if !c.tryImportMissing {
		return ImageDetails{}, fmt.Errorf("import of missing images disabled")
	}

	entry := ImageDetails{}

	if !c.client.ImageExists(ctx, tag) {
		logrus.Debugf("Importing artifact %s from docker registry", tag)
		err := c.client.Pull(ctx, ioutil.Discard, tag)
		if err != nil {
			return entry, err
		}
	} else {
		logrus.Debugf("Importing artifact %s from local docker", tag)
	}

	imageID, err := c.client.ImageID(ctx, a.ImageName)
	if err != nil {
		return entry, err
	}

	if imageID != "" {
		entry.ID = imageID
	}

	if digest, err := docker.RemoteDigest(tag, c.cfg); err == nil {
		logrus.Debugf("Added digest for %s to cache entry", tag)
		entry.Digest = digest
	}

	c.cacheMutex.Lock()
	c.artifactCache[hash] = entry
	c.cacheMutex.Unlock()
	return entry, nil
}
<commit_msg>tryImportMissing must lookup image with tag (#5165)<commit_after>/*
Copyright 2019 The Skaffold 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 cache

import (
	"context"
	"fmt"
	"io/ioutil"
	"sync"

	"github.com/sirupsen/logrus"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
)

func (c *cache) lookupArtifacts(ctx context.Context, tags tag.ImageTags, artifacts []*latest.Artifact) []cacheDetails {
	details := make([]cacheDetails, len(artifacts))
	// Create a new `artifactHasher` on every new dev loop.
	// This way every artifact hash is calculated at most once in a single dev loop, and recalculated on every dev loop.
	h := newArtifactHasherFunc(c.artifactGraph, c.lister, c.cfg.Mode())
	var wg sync.WaitGroup
	for i := range artifacts {
		wg.Add(1)

		i := i
		go func() {
			details[i] = c.lookup(ctx, artifacts[i], tags[artifacts[i].ImageName], h)
			wg.Done()
		}()
	}
	wg.Wait()

	return details
}

func (c *cache) lookup(ctx context.Context, a *latest.Artifact, tag string, h artifactHasher) cacheDetails {
	hash, err := h.hash(ctx, a)
	if err != nil {
		return failed{err: fmt.Errorf("getting hash for artifact %q: %s", a.ImageName, err)}
	}

	c.cacheMutex.RLock()
	entry, cacheHit := c.artifactCache[hash]
	c.cacheMutex.RUnlock()
	if !cacheHit {
		if entry, err = c.tryImport(ctx, tag, hash); err != nil {
			logrus.Debugf("Could not import artifact from Docker, building instead (%s)", err)
			return needsBuilding{hash: hash}
		}
	}

	if c.imagesAreLocal {
		return c.lookupLocal(ctx, hash, tag, entry)
	}
	return c.lookupRemote(ctx, hash, tag, entry)
}

func (c *cache) lookupLocal(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
	if entry.ID == "" {
		return needsBuilding{hash: hash}
	}

	// Check the imageID for the tag
	idForTag, err := c.client.ImageID(ctx, tag)
	if err != nil {
		// Rely on actionable errors thrown from pkg/skaffold/docker.LocalDaemon api.
		return failed{err: err}
	}

	// Image exists locally with the same tag
	if idForTag == entry.ID {
		return found{hash: hash}
	}

	// Image exists locally with a different tag
	if c.client.ImageExists(ctx, entry.ID) {
		return needsLocalTagging{hash: hash, tag: tag, imageID: entry.ID}
	}

	return needsBuilding{hash: hash}
}

func (c *cache) lookupRemote(ctx context.Context, hash, tag string, entry ImageDetails) cacheDetails {
	if remoteDigest, err := docker.RemoteDigest(tag, c.cfg); err == nil {
		// Image exists remotely with the same tag and digest
		if remoteDigest == entry.Digest {
			return found{hash: hash}
		}
	}

	// Image exists remotely with a different tag
	fqn := tag + "@" + entry.Digest // Actual tag will be ignored but we need the registry and the digest part of it.
	if remoteDigest, err := docker.RemoteDigest(fqn, c.cfg); err == nil {
		if remoteDigest == entry.Digest {
			return needsRemoteTagging{hash: hash, tag: tag, digest: entry.Digest}
		}
	}

	// Image exists locally
	if entry.ID != "" && c.client != nil && c.client.ImageExists(ctx, entry.ID) {
		return needsPushing{hash: hash, tag: tag, imageID: entry.ID}
	}

	return needsBuilding{hash: hash}
}

func (c *cache) tryImport(ctx context.Context, tag string, hash string) (ImageDetails, error) {
	if !c.tryImportMissing {
		return ImageDetails{}, fmt.Errorf("import of missing images disabled")
	}

	entry := ImageDetails{}

	if !c.client.ImageExists(ctx, tag) {
		logrus.Debugf("Importing artifact %s from docker registry", tag)
		err := c.client.Pull(ctx, ioutil.Discard, tag)
		if err != nil {
			return entry, err
		}
	} else {
		logrus.Debugf("Importing artifact %s from local docker", tag)
	}

	imageID, err := c.client.ImageID(ctx, tag)
	if err != nil {
		return entry, err
	}

	if imageID != "" {
		entry.ID = imageID
	}

	if digest, err := docker.RemoteDigest(tag, c.cfg); err == nil {
		logrus.Debugf("Added digest for %s to cache entry", tag)
		entry.Digest = digest
	}

	c.cacheMutex.Lock()
	c.artifactCache[hash] = entry
	c.cacheMutex.Unlock()
	return entry, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google LLC

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 runner

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch"
	"github.com/GoogleContainerTools/skaffold/testutil"
	clientgo "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
)

type TestBuilder struct {
	built  []build.Build
	errors []error
}

func (t *TestBuilder) Build(ctx context.Context, w io.Writer, tagger tag.Tagger, artifacts []*v1alpha2.Artifact) ([]build.Build, error) {
	if len(t.errors) > 0 {
		err := t.errors[0]
		t.errors = t.errors[1:]
		return nil, err
	}

	var builds []build.Build

	for _, artifact := range artifacts {
		builds = append(builds, build.Build{
			ImageName: artifact.ImageName,
		})
	}

	t.built = builds
	return builds, nil
}

type TestDeployer struct {
	deployed []build.Build
	err      error
}

func (t *TestDeployer) Dependencies() ([]string, error) {
	return nil, nil
}

func (t *TestDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Build) error {
	if t.err != nil {
		return t.err
	}

	t.deployed = builds
	return nil
}

func (t *TestDeployer) Cleanup(ctx context.Context, out io.Writer) error {
	return nil
}

func resetClient()                               { kubernetesClient = kubernetes.GetClientset }
func fakeGetClient() (clientgo.Interface, error) { return fake.NewSimpleClientset(), nil }

type TestWatcher struct {
	changes [][]string
}

func NewWatcherFactory(err error, changes ...[]string) watch.WatcherFactory {
	return func([]string) (watch.Watcher, error) {
		return &TestWatcher{
			changes: changes,
		}, err
	}
}

func (t *TestWatcher) Start(context context.Context, out io.Writer, onChange func([]string) error) error {
	for _, change := range t.changes {
		onChange(change)
	}
	return nil
}

type TestChanges struct {
	changes [][]*v1alpha2.Artifact
}

func (t *TestChanges) OnChange(action func(artifacts []*v1alpha2.Artifact)) {
	for _, artifacts := range t.changes {
		action(artifacts)
	}
}

func TestNewForConfig(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()
	var tests = []struct {
		description      string
		config           *v1alpha2.SkaffoldConfig
		shouldErr        bool
		expectedBuilder  build.Builder
		expectedDeployer deploy.Deployer
	}{
		{
			description: "local builder config",
			config: &config.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{ShaTagger: &v1alpha2.ShaTagger{}},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				},
				Deploy: v1alpha2.DeployConfig{
					DeployType: v1alpha2.DeployType{
						KubectlDeploy: &v1alpha2.KubectlDeploy{},
					},
				},
			},
			expectedBuilder:  &build.LocalBuilder{},
			expectedDeployer: &deploy.KubectlDeployer{},
		},
		{
			description: "bad tagger config",
			config: &v1alpha2.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				},
				Deploy: v1alpha2.DeployConfig{
					DeployType: v1alpha2.DeployType{
						KubectlDeploy: &v1alpha2.KubectlDeploy{},
					},
				},
			},
			shouldErr: true,
		},
		{
			description: "unknown builder",
			config: &v1alpha2.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{},
			},
			shouldErr:        true,
			expectedBuilder:  &build.LocalBuilder{},
			expectedDeployer: &deploy.KubectlDeployer{},
		},
		{
			description: "unknown tagger",
			config: &config.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				}},
			shouldErr:        true,
			expectedBuilder:  &build.LocalBuilder{},
			expectedDeployer: &deploy.KubectlDeployer{},
		},
		{
			description: "unknown deployer",
			config: &config.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{ShaTagger: &v1alpha2.ShaTagger{}},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				},
			},
			shouldErr: true,
		},
	}
	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			cfg, err := NewForConfig(&config.SkaffoldOptions{}, test.config)

			testutil.CheckError(t, test.shouldErr, err)
			if cfg != nil {
				b, d := WithTimings(test.expectedBuilder, test.expectedDeployer)

				testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, b, cfg.Builder)
				testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, d, cfg.Deployer)
			}
		})
	}
}

func TestRun(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()
	var tests = []struct {
		description string
		config      *config.SkaffoldConfig
		builder     build.Builder
		deployer    deploy.Deployer
		shouldErr   bool
	}{
		{
			description: "run no error",
			config:      &v1alpha2.SkaffoldConfig{},
			builder:     &TestBuilder{},
			deployer:    &TestDeployer{},
		},
		{
			description: "run build error",
			config:      &v1alpha2.SkaffoldConfig{},
			builder: &TestBuilder{
				errors: []error{fmt.Errorf("")},
			},
			shouldErr: true,
		},
		{
			description: "run deploy error",
			config: &v1alpha2.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					Artifacts: []*v1alpha2.Artifact{
						{
							ImageName: "test",
						},
					},
				},
			},
			builder: &TestBuilder{},
			deployer: &TestDeployer{
				err: fmt.Errorf(""),
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			runner := &SkaffoldRunner{
				Builder:  test.builder,
				Deployer: test.deployer,
				opts:     &config.SkaffoldOptions{},
				Tagger:   &tag.ChecksumTagger{},
			}
			err := runner.Run(context.Background(), ioutil.Discard, test.config.Build.Artifacts)

			testutil.CheckError(t, test.shouldErr, err)
		})
	}
}

func TestDev(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()
	var tests = []struct {
		description    string
		builder        build.Builder
		watcherFactory watch.WatcherFactory
		shouldErr      bool
	}{
		{
			description: "fails to build the first time",
			builder: &TestBuilder{
				errors: []error{fmt.Errorf("")},
			},
			watcherFactory: NewWatcherFactory(nil),
			shouldErr:      true,
		},
		{
			description: "ignore subsequent build errors",
			builder: &TestBuilder{
				errors: []error{nil, fmt.Errorf("")},
			},
			watcherFactory: NewWatcherFactory(nil, nil),
		},
		{
			description:    "bad watch dev mode",
			builder:        &TestBuilder{},
			watcherFactory: NewWatcherFactory(fmt.Errorf("")),
			shouldErr:      true,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			runner := &SkaffoldRunner{
				Builder:              test.builder,
				Deployer:             &TestDeployer{},
				opts:                 &config.SkaffoldOptions{},
				Tagger:               &tag.ChecksumTagger{},
				DependencyMapFactory: build.NewDependencyMap,
				WatcherFactory:       test.watcherFactory,
			}
			err := runner.Dev(context.Background(), ioutil.Discard, nil)

			testutil.CheckError(t, test.shouldErr, err)
		})
	}
}

func TestBuildAndDeployAllArtifacts(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()

	builder := &TestBuilder{}
	deployer := &TestDeployer{}
	artifacts := []*v1alpha2.Artifact{
		{ImageName: "image1"},
		{ImageName: "image2"},
	}
	pathToArtifacts := map[string][]*v1alpha2.Artifact{
		"path1": artifacts[0:1],
		"path2": artifacts[1:],
	}

	runner := &SkaffoldRunner{
		opts:     &config.SkaffoldOptions{},
		Builder:  builder,
		Deployer: deployer,
		DependencyMapFactory: func(artifacts []*v1alpha2.Artifact) (*build.DependencyMap, error) {
			return build.NewExplicitDependencyMap(artifacts, pathToArtifacts), nil
		},
	}

	ctx := context.Background()

	// All artifacts are changed
	runner.WatcherFactory = NewWatcherFactory(nil, []string{"path1", "path2"})
	err := runner.Dev(ctx, ioutil.Discard, artifacts)

	if err != nil {
		t.Errorf("Didn't expect an error. Got %s", err)
	}
	if len(builder.built) != 2 {
		t.Errorf("Expected 2 artifacts to be built. Got %d", len(builder.built))
	}
	if len(deployer.deployed) != 2 {
		t.Errorf("Expected 2 artifacts to be deployed. Got %d", len(deployer.deployed))
	}

	// Only one is changed
	runner.WatcherFactory = NewWatcherFactory(nil, []string{"path2"})
	err = runner.Dev(ctx, ioutil.Discard, artifacts)

	if err != nil {
		t.Errorf("Didn't expect an error. Got %s", err)
	}
	if len(builder.built) != 1 {
		t.Errorf("Expected 1 artifact to be built. Got %d", len(builder.built))
	}
	if len(deployer.deployed) != 2 {
		t.Errorf("Expected 2 artifacts to be deployed. Got %d", len(deployer.deployed))
	}
}
<commit_msg>Remove unused test helper<commit_after>/*
Copyright 2018 Google LLC

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 runner

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/tag"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2"
	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/watch"
	"github.com/GoogleContainerTools/skaffold/testutil"
	clientgo "k8s.io/client-go/kubernetes"
	"k8s.io/client-go/kubernetes/fake"
)

type TestBuilder struct {
	built  []build.Build
	errors []error
}

func (t *TestBuilder) Build(ctx context.Context, w io.Writer, tagger tag.Tagger, artifacts []*v1alpha2.Artifact) ([]build.Build, error) {
	if len(t.errors) > 0 {
		err := t.errors[0]
		t.errors = t.errors[1:]
		return nil, err
	}

	var builds []build.Build

	for _, artifact := range artifacts {
		builds = append(builds, build.Build{
			ImageName: artifact.ImageName,
		})
	}

	t.built = builds
	return builds, nil
}

type TestDeployer struct {
	deployed []build.Build
	err      error
}

func (t *TestDeployer) Dependencies() ([]string, error) {
	return nil, nil
}

func (t *TestDeployer) Deploy(ctx context.Context, out io.Writer, builds []build.Build) error {
	if t.err != nil {
		return t.err
	}

	t.deployed = builds
	return nil
}

func (t *TestDeployer) Cleanup(ctx context.Context, out io.Writer) error {
	return nil
}

func resetClient()                               { kubernetesClient = kubernetes.GetClientset }
func fakeGetClient() (clientgo.Interface, error) { return fake.NewSimpleClientset(), nil }

type TestWatcher struct {
	changes [][]string
}

func NewWatcherFactory(err error, changes ...[]string) watch.WatcherFactory {
	return func([]string) (watch.Watcher, error) {
		return &TestWatcher{
			changes: changes,
		}, err
	}
}

func (t *TestWatcher) Start(context context.Context, out io.Writer, onChange func([]string) error) error {
	for _, change := range t.changes {
		onChange(change)
	}
	return nil
}

func TestNewForConfig(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()
	var tests = []struct {
		description      string
		config           *v1alpha2.SkaffoldConfig
		shouldErr        bool
		expectedBuilder  build.Builder
		expectedDeployer deploy.Deployer
	}{
		{
			description: "local builder config",
			config: &config.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{ShaTagger: &v1alpha2.ShaTagger{}},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				},
				Deploy: v1alpha2.DeployConfig{
					DeployType: v1alpha2.DeployType{
						KubectlDeploy: &v1alpha2.KubectlDeploy{},
					},
				},
			},
			expectedBuilder:  &build.LocalBuilder{},
			expectedDeployer: &deploy.KubectlDeployer{},
		},
		{
			description: "bad tagger config",
			config: &v1alpha2.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				},
				Deploy: v1alpha2.DeployConfig{
					DeployType: v1alpha2.DeployType{
						KubectlDeploy: &v1alpha2.KubectlDeploy{},
					},
				},
			},
			shouldErr: true,
		},
		{
			description: "unknown builder",
			config: &v1alpha2.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{},
			},
			shouldErr:        true,
			expectedBuilder:  &build.LocalBuilder{},
			expectedDeployer: &deploy.KubectlDeployer{},
		},
		{
			description: "unknown tagger",
			config: &config.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				}},
			shouldErr:        true,
			expectedBuilder:  &build.LocalBuilder{},
			expectedDeployer: &deploy.KubectlDeployer{},
		},
		{
			description: "unknown deployer",
			config: &config.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					TagPolicy: v1alpha2.TagPolicy{ShaTagger: &v1alpha2.ShaTagger{}},
					BuildType: v1alpha2.BuildType{
						LocalBuild: &v1alpha2.LocalBuild{},
					},
				},
			},
			shouldErr: true,
		},
	}
	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			cfg, err := NewForConfig(&config.SkaffoldOptions{}, test.config)

			testutil.CheckError(t, test.shouldErr, err)
			if cfg != nil {
				b, d := WithTimings(test.expectedBuilder, test.expectedDeployer)

				testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, b, cfg.Builder)
				testutil.CheckErrorAndTypeEquality(t, test.shouldErr, err, d, cfg.Deployer)
			}
		})
	}
}

func TestRun(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()
	var tests = []struct {
		description string
		config      *config.SkaffoldConfig
		builder     build.Builder
		deployer    deploy.Deployer
		shouldErr   bool
	}{
		{
			description: "run no error",
			config:      &v1alpha2.SkaffoldConfig{},
			builder:     &TestBuilder{},
			deployer:    &TestDeployer{},
		},
		{
			description: "run build error",
			config:      &v1alpha2.SkaffoldConfig{},
			builder: &TestBuilder{
				errors: []error{fmt.Errorf("")},
			},
			shouldErr: true,
		},
		{
			description: "run deploy error",
			config: &v1alpha2.SkaffoldConfig{
				Build: v1alpha2.BuildConfig{
					Artifacts: []*v1alpha2.Artifact{
						{
							ImageName: "test",
						},
					},
				},
			},
			builder: &TestBuilder{},
			deployer: &TestDeployer{
				err: fmt.Errorf(""),
			},
			shouldErr: true,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			runner := &SkaffoldRunner{
				Builder:  test.builder,
				Deployer: test.deployer,
				opts:     &config.SkaffoldOptions{},
				Tagger:   &tag.ChecksumTagger{},
			}
			err := runner.Run(context.Background(), ioutil.Discard, test.config.Build.Artifacts)

			testutil.CheckError(t, test.shouldErr, err)
		})
	}
}

func TestDev(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()
	var tests = []struct {
		description    string
		builder        build.Builder
		watcherFactory watch.WatcherFactory
		shouldErr      bool
	}{
		{
			description: "fails to build the first time",
			builder: &TestBuilder{
				errors: []error{fmt.Errorf("")},
			},
			watcherFactory: NewWatcherFactory(nil),
			shouldErr:      true,
		},
		{
			description: "ignore subsequent build errors",
			builder: &TestBuilder{
				errors: []error{nil, fmt.Errorf("")},
			},
			watcherFactory: NewWatcherFactory(nil, nil),
		},
		{
			description:    "bad watch dev mode",
			builder:        &TestBuilder{},
			watcherFactory: NewWatcherFactory(fmt.Errorf("")),
			shouldErr:      true,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			runner := &SkaffoldRunner{
				Builder:              test.builder,
				Deployer:             &TestDeployer{},
				opts:                 &config.SkaffoldOptions{},
				Tagger:               &tag.ChecksumTagger{},
				DependencyMapFactory: build.NewDependencyMap,
				WatcherFactory:       test.watcherFactory,
			}
			err := runner.Dev(context.Background(), ioutil.Discard, nil)

			testutil.CheckError(t, test.shouldErr, err)
		})
	}
}

func TestBuildAndDeployAllArtifacts(t *testing.T) {
	kubernetesClient = fakeGetClient
	defer resetClient()

	builder := &TestBuilder{}
	deployer := &TestDeployer{}
	artifacts := []*v1alpha2.Artifact{
		{ImageName: "image1"},
		{ImageName: "image2"},
	}
	pathToArtifacts := map[string][]*v1alpha2.Artifact{
		"path1": artifacts[0:1],
		"path2": artifacts[1:],
	}

	runner := &SkaffoldRunner{
		opts:     &config.SkaffoldOptions{},
		Builder:  builder,
		Deployer: deployer,
		DependencyMapFactory: func(artifacts []*v1alpha2.Artifact) (*build.DependencyMap, error) {
			return build.NewExplicitDependencyMap(artifacts, pathToArtifacts), nil
		},
	}

	ctx := context.Background()

	// All artifacts are changed
	runner.WatcherFactory = NewWatcherFactory(nil, []string{"path1", "path2"})
	err := runner.Dev(ctx, ioutil.Discard, artifacts)

	if err != nil {
		t.Errorf("Didn't expect an error. Got %s", err)
	}
	if len(builder.built) != 2 {
		t.Errorf("Expected 2 artifacts to be built. Got %d", len(builder.built))
	}
	if len(deployer.deployed) != 2 {
		t.Errorf("Expected 2 artifacts to be deployed. Got %d", len(deployer.deployed))
	}

	// Only one is changed
	runner.WatcherFactory = NewWatcherFactory(nil, []string{"path2"})
	err = runner.Dev(ctx, ioutil.Discard, artifacts)

	if err != nil {
		t.Errorf("Didn't expect an error. Got %s", err)
	}
	if len(builder.built) != 1 {
		t.Errorf("Expected 1 artifact to be built. Got %d", len(builder.built))
	}
	if len(deployer.deployed) != 2 {
		t.Errorf("Expected 2 artifacts to be deployed. Got %d", len(deployer.deployed))
	}
}
<|endoftext|>


<commit_before>/*
Package grpc provides user grpc server
*/
package grpc

import (
	"context"
	"fmt"

	"github.com/golang/protobuf/ptypes/empty"
	"github.com/vardius/go-api-boilerplate/pkg/common/application/jwt"
	"github.com/vardius/go-api-boilerplate/pkg/common/infrastructure/commandbus"
	"github.com/vardius/go-api-boilerplate/pkg/common/infrastructure/eventbus"
	"github.com/vardius/go-api-boilerplate/pkg/common/infrastructure/eventstore"
	"github.com/vardius/go-api-boilerplate/pkg/user/application"
	"github.com/vardius/go-api-boilerplate/pkg/user/domain/user"
	"github.com/vardius/go-api-boilerplate/pkg/user/infrastructure/proto"
	"github.com/vardius/go-api-boilerplate/pkg/user/infrastructure/repository"
)

type userServer struct {
	commandBus commandbus.CommandBus
	eventBus   eventbus.EventBus
	eventStore eventstore.EventStore
	jwt        jwt.Jwt
}

// New returns new user server object
func New(cb commandbus.CommandBus, eb eventbus.EventBus, es eventstore.EventStore, j jwt.Jwt) proto.UserServer {
	s := &userServer{cb, eb, es, j}

	registerCommandHandlers(cb, es, eb)
	registerEventHandlers(eb)

	return s
}

// DispatchCommand implements proto.UserServer interface
func (s *userServer) DispatchCommand(ctx context.Context, cmd *proto.DispatchCommandRequest) (*empty.Empty, error) {
	out := make(chan error)
	defer close(out)

	go func() {
		c, err := buildDomainCommand(ctx, cmd)
		if err != nil {
			out <- err
			return
		}

		s.commandBus.Publish(ctx, fmt.Sprintf("%T", c), c, out)
	}()

	select {
	case <-ctx.Done():
		return new(empty.Empty), ctx.Err()
	case err := <-out:
		return new(empty.Empty), err
	}
}

func registerCommandHandlers(cb commandbus.CommandBus, es eventstore.EventStore, eb eventbus.EventBus) {
	repository := repository.NewUser(es, eb)

	cb.Subscribe(fmt.Sprintf("%T", &user.RegisterWithEmail{}), user.OnRegisterWithEmail(repository))
	cb.Subscribe(fmt.Sprintf("%T", &user.RegisterWithGoogle{}), user.OnRegisterWithGoogle(repository))
	cb.Subscribe(fmt.Sprintf("%T", &user.RegisterWithFacebook{}), user.OnRegisterWithFacebook(repository))
	cb.Subscribe(fmt.Sprintf("%T", &user.ChangeEmailAddress{}), user.OnChangeEmailAddress(repository))
}

func registerEventHandlers(eb eventbus.EventBus) {
	eb.Subscribe(fmt.Sprintf("%T", &user.WasRegisteredWithEmail{}), application.WhenUserWasRegisteredWithEmail)
	eb.Subscribe(fmt.Sprintf("%T", &user.WasRegisteredWithGoogle{}), application.WhenUserWasRegisteredWithGoogle)
	eb.Subscribe(fmt.Sprintf("%T", &user.WasRegisteredWithFacebook{}), application.WhenUserWasRegisteredWithFacebook)
	eb.Subscribe(fmt.Sprintf("%T", &user.EmailAddressWasChanged{}), application.WhenUserEmailAddressWasChanged)
}
<commit_msg>Handle panic exceptions<commit_after>/*
Package grpc provides user grpc server
*/
package grpc

import (
	"context"
	"fmt"

	"github.com/golang/protobuf/ptypes/empty"
	"github.com/vardius/go-api-boilerplate/pkg/common/application/errors"
	"github.com/vardius/go-api-boilerplate/pkg/common/application/jwt"
	"github.com/vardius/go-api-boilerplate/pkg/common/infrastructure/commandbus"
	"github.com/vardius/go-api-boilerplate/pkg/common/infrastructure/eventbus"
	"github.com/vardius/go-api-boilerplate/pkg/common/infrastructure/eventstore"
	"github.com/vardius/go-api-boilerplate/pkg/user/application"
	"github.com/vardius/go-api-boilerplate/pkg/user/domain/user"
	"github.com/vardius/go-api-boilerplate/pkg/user/infrastructure/proto"
	"github.com/vardius/go-api-boilerplate/pkg/user/infrastructure/repository"
)

type userServer struct {
	commandBus commandbus.CommandBus
	eventBus   eventbus.EventBus
	eventStore eventstore.EventStore
	jwt        jwt.Jwt
}

// New returns new user server object
func New(cb commandbus.CommandBus, eb eventbus.EventBus, es eventstore.EventStore, j jwt.Jwt) proto.UserServer {
	s := &userServer{cb, eb, es, j}

	registerCommandHandlers(cb, es, eb)
	registerEventHandlers(eb)

	return s
}

// DispatchCommand implements proto.UserServer interface
func (s *userServer) DispatchCommand(ctx context.Context, cmd *proto.DispatchCommandRequest) (*empty.Empty, error) {
	out := make(chan error)
	defer close(out)

	go func() {
		defer func() {
			if rec := recover(); rec != nil {
				out <- errors.Newf(errors.INTERNAL, "Recovered in f %v", rec)
			}
		}()

		c, err := buildDomainCommand(ctx, cmd)
		if err != nil {
			out <- err
			return
		}

		s.commandBus.Publish(ctx, fmt.Sprintf("%T", c), c, out)
	}()

	select {
	case <-ctx.Done():
		return new(empty.Empty), ctx.Err()
	case err := <-out:
		return new(empty.Empty), err
	}
}

func registerCommandHandlers(cb commandbus.CommandBus, es eventstore.EventStore, eb eventbus.EventBus) {
	repository := repository.NewUser(es, eb)

	cb.Subscribe(fmt.Sprintf("%T", &user.RegisterWithEmail{}), user.OnRegisterWithEmail(repository))
	cb.Subscribe(fmt.Sprintf("%T", &user.RegisterWithGoogle{}), user.OnRegisterWithGoogle(repository))
	cb.Subscribe(fmt.Sprintf("%T", &user.RegisterWithFacebook{}), user.OnRegisterWithFacebook(repository))
	cb.Subscribe(fmt.Sprintf("%T", &user.ChangeEmailAddress{}), user.OnChangeEmailAddress(repository))
}

func registerEventHandlers(eb eventbus.EventBus) {
	eb.Subscribe(fmt.Sprintf("%T", &user.WasRegisteredWithEmail{}), application.WhenUserWasRegisteredWithEmail)
	eb.Subscribe(fmt.Sprintf("%T", &user.WasRegisteredWithGoogle{}), application.WhenUserWasRegisteredWithGoogle)
	eb.Subscribe(fmt.Sprintf("%T", &user.WasRegisteredWithFacebook{}), application.WhenUserWasRegisteredWithFacebook)
	eb.Subscribe(fmt.Sprintf("%T", &user.EmailAddressWasChanged{}), application.WhenUserEmailAddressWasChanged)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 validation

import (
	"fmt"
	"time"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
)

const (
	// How often to Poll pods, nodes and claims.
	Poll = 2 * time.Second

	// How long to try single API calls (like 'get' or 'list'). Used to prevent
	// transient failures
	// TODO: client should not apply this timeout to Watch calls. Increased from 30s until that is fixed.
	SingleCallTimeout = 5 * time.Minute
)

// TODO: Rename to NodeValidator; probably just convert to utility functions
// NodeAPIAdapter used to retrieve information about Nodes in K8s
// TODO: should we pool the api client connection? My initial thought is no.
type NodeAPIAdapter struct {
	// K8s API client this sucker talks to K8s directly - not kubectl, hard api call
	client kubernetes.Interface

	//TODO: convert to arg on WaitForNodeToBe
	// K8s timeout on method call
	timeout time.Duration
}

func NewNodeAPIAdapter(client kubernetes.Interface, timeout time.Duration) (*NodeAPIAdapter, error) {
	if client == nil {
		return nil, fmt.Errorf("client not provided")
	}
	return &NodeAPIAdapter{
		client:  client,
		timeout: timeout,
	}, nil
}

// GetAllNodes is a access to get all nodes from a cluster api
func (nodeAA *NodeAPIAdapter) GetAllNodes() (nodes *v1.NodeList, err error) {
	opts := metav1.ListOptions{}
	nodes, err = nodeAA.client.CoreV1().Nodes().List(opts)
	if err != nil {
		glog.V(4).Infof("getting nodes failed for node %v", err)
		return nil, err
	}

	return nodes, nil
}

// GetReadySchedulableNodesOrDie addresses the common use case of getting nodes you can do work on.
// 1) Needs to be schedulable.
// 2) Needs to be ready.
// If EITHER 1 or 2 is not true, most tests will want to ignore the node entirely.
func (nodeAA *NodeAPIAdapter) GetReadySchedulableNodes() ([]*v1.Node, error) {
	nodeList, err := nodeAA.waitListSchedulableNodes()
	if err != nil {
		return nil, fmt.Errorf("error from listing schedulable nodes: %v", err)
	}

	// previous tests may have cause failures of some nodes. Let's skip
	// 'Not Ready' nodes, just in case (there is no need to fail the test).
	filtered := FilterNodes(nodeList, isNodeSchedulable)
	return filtered, err

}

// WaitForNodeToBeReady returns whether node name is ready within timeout.
func (nodeAA *NodeAPIAdapter) WaitForNodeToBeReady(nodeName string) (bool, error) {
	return nodeAA.WaitForNodeToBe(nodeName, v1.NodeReady, v1.ConditionTrue)
}

// WaitForNodeToBeNotReady returns whether node is not ready (i.e. the
// readiness condition is anything but ready, e.g false or unknown) within
// timeout.
func (nodeAA *NodeAPIAdapter) WaitForNodeToBeNotReady(nodeName string) (bool, error) {
	return nodeAA.WaitForNodeToBe(nodeName, v1.NodeReady, v1.ConditionFalse, v1.ConditionUnknown)
}

// WaitForNodeToBe returns whether the names node condition state matches one of the expected values,
// within timeout.
func (nodeAA *NodeAPIAdapter) WaitForNodeToBe(nodeName string, conditionType v1.NodeConditionType, expected ...v1.ConditionStatus) (bool, error) {
	if nodeName == "" {
		return false, fmt.Errorf("nodeName was empty")
	}

	glog.V(4).Infof("Waiting up to %v for node %s condition %s to be %v", nodeAA.timeout, nodeName, conditionType, expected)

	var cond *v1.NodeCondition
	err := wait.PollImmediate(Poll, nodeAA.timeout, func() (bool, error) {
		node, err := nodeAA.client.Core().Nodes().Get(nodeName, metav1.GetOptions{})
		// FIXME this is not erroring on 500's for instance.  We will keep looping
		if err != nil {
			// TODO: Check if e.g. NotFound
			glog.V(4).Infof("Couldn't get node %s: %v", nodeName, err)
			return false, nil
		}
		cond = findNodeCondition(node, conditionType)
		if cond == nil {
			return false, nil
		}
		return conditionMatchesExpected(cond, expected...), nil
	})
	if err != nil {
		if err == wait.ErrWaitTimeout {
			glog.V(4).Infof("Node %s didn't reach desired %s condition status (%v) within %v.  Actual=%v", nodeName, conditionType, expected, nodeAA.timeout, cond)
			return false, nil
		}
		// TODO: Should return error
		return false, nil
	} else {
		return true, nil
	}
}

// IsNodeConditionUnset check that node condition is not set
func isNodeConditionUnset(node *v1.Node, conditionType v1.NodeConditionType) bool {
	cond := findNodeCondition(node, conditionType)
	return cond == nil
}

func FilterNodes(nodeList *v1.NodeList, fn func(node *v1.Node) bool) []*v1.Node {
	var matches []*v1.Node
	for i := range nodeList.Items {
		node := &nodeList.Items[i]
		if fn(node) {
			matches = append(matches, node)
		}
	}
	return matches
}

// waitListSchedulableNodes is a wrapper around listing nodes supporting retries.
func (nodeAA *NodeAPIAdapter) waitListSchedulableNodes() (*v1.NodeList, error) {
	var nodeList *v1.NodeList
	err := wait.PollImmediate(Poll, SingleCallTimeout, func() (bool, error) {
		var err error
		nodeList, err = nodeAA.client.Core().Nodes().List(metav1.ListOptions{FieldSelector: "spec.unschedulable=false"})
		if err != nil {
			// error logging TODO
			return false, err
		}
		return err == nil, nil
	})

	if err != nil {
		// TODO logging
		return nil, err
	}
	return nodeList, err
}

func findNodeCondition(node *v1.Node, conditionType v1.NodeConditionType) *v1.NodeCondition {
	for i := range node.Status.Conditions {
		cond := &node.Status.Conditions[i]
		if cond.Type == conditionType {
			return cond
		}
	}
	return nil
}

func conditionMatchesExpected(cond *v1.NodeCondition, expected ...v1.ConditionStatus) bool {
	for _, e := range expected {
		if cond.Status == e {
			return true
		}
	}
	return false
}
func isNodeConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, expected ...v1.ConditionStatus) bool {
	cond := findNodeCondition(node, conditionType)
	if cond == nil {
		glog.V(4).Infof("Couldn't find condition %v on node %v", conditionType, node.Name)
		return false
	}

	if conditionMatchesExpected(cond, expected...) {
		return true
	}

	glog.V(4).Infof(
		"Condition %s of node %s is %v instead of %v. Reason: %v, message: %v",
		conditionType, node.Name, cond.Status, expected, cond.Reason, cond.Message)
	return false
}

// Node is schedulable if:
// 1) doesn't have "unschedulable" field set
// 2) it's Ready condition is set to true
// 3) doesn't have NetworkUnavailable condition set to true
func isNodeSchedulable(node *v1.Node) bool {
	nodeReady := isNodeConditionSetAsExpected(node, v1.NodeReady, v1.ConditionTrue)

	// TODO: Combine
	networkUnavailable := isNodeConditionUnset(node, v1.NodeNetworkUnavailable)
	networkUnavailableSilent := isNodeConditionSetAsExpected(node, v1.NodeNetworkUnavailable, v1.ConditionFalse, v1.ConditionUnknown)

	networkReady := networkUnavailable || networkUnavailableSilent

	return !node.Spec.Unschedulable && nodeReady && networkReady
}

// Get The Status of a Node
func GetNodeConditionStatus(node *v1.Node) v1.ConditionStatus {
	cond := findNodeCondition(node, v1.NodeReady)
	if cond != nil {
		return cond.Status
	}
	return v1.ConditionUnknown
}

// Node is ready if:
// 1) its Ready condition is set to true
// 2) doesn't have NetworkUnavailable condition set to true
func IsNodeOrMasterReady(node *v1.Node) bool {
	nodeReady := isNodeConditionSetAsExpected(node, v1.NodeReady, v1.ConditionTrue)

	// TODO: Combine
	networkUnavailable := isNodeConditionUnset(node, v1.NodeNetworkUnavailable)
	networkUnavailableSilent := isNodeConditionSetAsExpected(node, v1.NodeNetworkUnavailable, v1.ConditionFalse, v1.ConditionUnknown)

	networkReady := networkUnavailable || networkUnavailableSilent

	return nodeReady && networkReady
}
<commit_msg>Updating K8s API calls to use CoreV1 kops node adapter<commit_after>/*
Copyright 2016 The Kubernetes 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 validation

import (
	"fmt"
	"time"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
)

const (
	// How often to Poll pods, nodes and claims.
	Poll = 2 * time.Second

	// How long to try single API calls (like 'get' or 'list'). Used to prevent
	// transient failures
	// TODO: client should not apply this timeout to Watch calls. Increased from 30s until that is fixed.
	SingleCallTimeout = 5 * time.Minute
)

// TODO: Rename to NodeValidator; probably just convert to utility functions
// NodeAPIAdapter used to retrieve information about Nodes in K8s
// TODO: should we pool the api client connection? My initial thought is no.
type NodeAPIAdapter struct {
	// K8s API client this sucker talks to K8s directly - not kubectl, hard api call
	client kubernetes.Interface

	//TODO: convert to arg on WaitForNodeToBe
	// K8s timeout on method call
	timeout time.Duration
}

func NewNodeAPIAdapter(client kubernetes.Interface, timeout time.Duration) (*NodeAPIAdapter, error) {
	if client == nil {
		return nil, fmt.Errorf("client not provided")
	}
	return &NodeAPIAdapter{
		client:  client,
		timeout: timeout,
	}, nil
}

// GetAllNodes is a access to get all nodes from a cluster api
func (nodeAA *NodeAPIAdapter) GetAllNodes() (nodes *v1.NodeList, err error) {
	opts := metav1.ListOptions{}
	nodes, err = nodeAA.client.CoreV1().Nodes().List(opts)
	if err != nil {
		glog.V(4).Infof("getting nodes failed for node %v", err)
		return nil, err
	}

	return nodes, nil
}

// GetReadySchedulableNodesOrDie addresses the common use case of getting nodes you can do work on.
// 1) Needs to be schedulable.
// 2) Needs to be ready.
// If EITHER 1 or 2 is not true, most tests will want to ignore the node entirely.
func (nodeAA *NodeAPIAdapter) GetReadySchedulableNodes() ([]*v1.Node, error) {
	nodeList, err := nodeAA.waitListSchedulableNodes()
	if err != nil {
		return nil, fmt.Errorf("error from listing schedulable nodes: %v", err)
	}

	// previous tests may have cause failures of some nodes. Let's skip
	// 'Not Ready' nodes, just in case (there is no need to fail the test).
	filtered := FilterNodes(nodeList, isNodeSchedulable)
	return filtered, err

}

// WaitForNodeToBeReady returns whether node name is ready within timeout.
func (nodeAA *NodeAPIAdapter) WaitForNodeToBeReady(nodeName string) (bool, error) {
	return nodeAA.WaitForNodeToBe(nodeName, v1.NodeReady, v1.ConditionTrue)
}

// WaitForNodeToBeNotReady returns whether node is not ready (i.e. the
// readiness condition is anything but ready, e.g false or unknown) within
// timeout.
func (nodeAA *NodeAPIAdapter) WaitForNodeToBeNotReady(nodeName string) (bool, error) {
	return nodeAA.WaitForNodeToBe(nodeName, v1.NodeReady, v1.ConditionFalse, v1.ConditionUnknown)
}

// WaitForNodeToBe returns whether the names node condition state matches one of the expected values,
// within timeout.
func (nodeAA *NodeAPIAdapter) WaitForNodeToBe(nodeName string, conditionType v1.NodeConditionType, expected ...v1.ConditionStatus) (bool, error) {
	if nodeName == "" {
		return false, fmt.Errorf("nodeName was empty")
	}

	glog.V(4).Infof("Waiting up to %v for node %s condition %s to be %v", nodeAA.timeout, nodeName, conditionType, expected)

	var cond *v1.NodeCondition
	err := wait.PollImmediate(Poll, nodeAA.timeout, func() (bool, error) {
		node, err := nodeAA.client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
		// FIXME this is not erroring on 500's for instance.  We will keep looping
		if err != nil {
			// TODO: Check if e.g. NotFound
			glog.V(4).Infof("Couldn't get node %s: %v", nodeName, err)
			return false, nil
		}
		cond = findNodeCondition(node, conditionType)
		if cond == nil {
			return false, nil
		}
		return conditionMatchesExpected(cond, expected...), nil
	})
	if err != nil {
		if err == wait.ErrWaitTimeout {
			glog.V(4).Infof("Node %s didn't reach desired %s condition status (%v) within %v.  Actual=%v", nodeName, conditionType, expected, nodeAA.timeout, cond)
			return false, nil
		}
		// TODO: Should return error
		return false, nil
	} else {
		return true, nil
	}
}

// IsNodeConditionUnset check that node condition is not set
func isNodeConditionUnset(node *v1.Node, conditionType v1.NodeConditionType) bool {
	cond := findNodeCondition(node, conditionType)
	return cond == nil
}

func FilterNodes(nodeList *v1.NodeList, fn func(node *v1.Node) bool) []*v1.Node {
	var matches []*v1.Node
	for i := range nodeList.Items {
		node := &nodeList.Items[i]
		if fn(node) {
			matches = append(matches, node)
		}
	}
	return matches
}

// waitListSchedulableNodes is a wrapper around listing nodes supporting retries.
func (nodeAA *NodeAPIAdapter) waitListSchedulableNodes() (*v1.NodeList, error) {
	var nodeList *v1.NodeList
	err := wait.PollImmediate(Poll, SingleCallTimeout, func() (bool, error) {
		var err error
		nodeList, err = nodeAA.client.CoreV1().Nodes().List(metav1.ListOptions{FieldSelector: "spec.unschedulable=false"})
		if err != nil {
			// error logging TODO
			return false, err
		}
		return err == nil, nil
	})

	if err != nil {
		// TODO logging
		return nil, err
	}
	return nodeList, err
}

func findNodeCondition(node *v1.Node, conditionType v1.NodeConditionType) *v1.NodeCondition {
	for i := range node.Status.Conditions {
		cond := &node.Status.Conditions[i]
		if cond.Type == conditionType {
			return cond
		}
	}
	return nil
}

func conditionMatchesExpected(cond *v1.NodeCondition, expected ...v1.ConditionStatus) bool {
	for _, e := range expected {
		if cond.Status == e {
			return true
		}
	}
	return false
}
func isNodeConditionSetAsExpected(node *v1.Node, conditionType v1.NodeConditionType, expected ...v1.ConditionStatus) bool {
	cond := findNodeCondition(node, conditionType)
	if cond == nil {
		glog.V(4).Infof("Couldn't find condition %v on node %v", conditionType, node.Name)
		return false
	}

	if conditionMatchesExpected(cond, expected...) {
		return true
	}

	glog.V(4).Infof(
		"Condition %s of node %s is %v instead of %v. Reason: %v, message: %v",
		conditionType, node.Name, cond.Status, expected, cond.Reason, cond.Message)
	return false
}

// Node is schedulable if:
// 1) doesn't have "unschedulable" field set
// 2) it's Ready condition is set to true
// 3) doesn't have NetworkUnavailable condition set to true
func isNodeSchedulable(node *v1.Node) bool {
	nodeReady := isNodeConditionSetAsExpected(node, v1.NodeReady, v1.ConditionTrue)

	// TODO: Combine
	networkUnavailable := isNodeConditionUnset(node, v1.NodeNetworkUnavailable)
	networkUnavailableSilent := isNodeConditionSetAsExpected(node, v1.NodeNetworkUnavailable, v1.ConditionFalse, v1.ConditionUnknown)

	networkReady := networkUnavailable || networkUnavailableSilent

	return !node.Spec.Unschedulable && nodeReady && networkReady
}

// Get The Status of a Node
func GetNodeConditionStatus(node *v1.Node) v1.ConditionStatus {
	cond := findNodeCondition(node, v1.NodeReady)
	if cond != nil {
		return cond.Status
	}
	return v1.ConditionUnknown
}

// Node is ready if:
// 1) its Ready condition is set to true
// 2) doesn't have NetworkUnavailable condition set to true
func IsNodeOrMasterReady(node *v1.Node) bool {
	nodeReady := isNodeConditionSetAsExpected(node, v1.NodeReady, v1.ConditionTrue)

	// TODO: Combine
	networkUnavailable := isNodeConditionUnset(node, v1.NodeNetworkUnavailable)
	networkUnavailableSilent := isNodeConditionSetAsExpected(node, v1.NodeNetworkUnavailable, v1.ConditionFalse, v1.ConditionUnknown)

	networkReady := networkUnavailable || networkUnavailableSilent

	return nodeReady && networkReady
}
<|endoftext|>