mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge branch 'master' of github.com:asridharan/kubernetes into fix_docker_doc
This commit is contained in:
		
							
								
								
									
										4
									
								
								Godeps/Godeps.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								Godeps/Godeps.json
									
									
									
										generated
									
									
									
								
							@@ -425,6 +425,10 @@
 | 
			
		||||
			"ImportPath": "github.com/mitchellh/mapstructure",
 | 
			
		||||
			"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
 | 
			
		||||
		},
 | 
			
		||||
                {
 | 
			
		||||
                        "ImportPath": "github.com/mxk/go-flowrate/flowrate",
 | 
			
		||||
                        "Rev": "cca7078d478f8520f85629ad7c68962d31ed7682"
 | 
			
		||||
                },
 | 
			
		||||
		{
 | 
			
		||||
			"ImportPath": "github.com/onsi/ginkgo",
 | 
			
		||||
			"Comment": "v1.2.0-6-gd981d36",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										267
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/flowrate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										267
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/flowrate.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,267 @@
 | 
			
		||||
//
 | 
			
		||||
// Written by Maxim Khitrov (November 2012)
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Package flowrate provides the tools for monitoring and limiting the flow rate
 | 
			
		||||
// of an arbitrary data stream.
 | 
			
		||||
package flowrate
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Monitor monitors and limits the transfer rate of a data stream.
 | 
			
		||||
type Monitor struct {
 | 
			
		||||
	mu      sync.Mutex    // Mutex guarding access to all internal fields
 | 
			
		||||
	active  bool          // Flag indicating an active transfer
 | 
			
		||||
	start   time.Duration // Transfer start time (clock() value)
 | 
			
		||||
	bytes   int64         // Total number of bytes transferred
 | 
			
		||||
	samples int64         // Total number of samples taken
 | 
			
		||||
 | 
			
		||||
	rSample float64 // Most recent transfer rate sample (bytes per second)
 | 
			
		||||
	rEMA    float64 // Exponential moving average of rSample
 | 
			
		||||
	rPeak   float64 // Peak transfer rate (max of all rSamples)
 | 
			
		||||
	rWindow float64 // rEMA window (seconds)
 | 
			
		||||
 | 
			
		||||
	sBytes int64         // Number of bytes transferred since sLast
 | 
			
		||||
	sLast  time.Duration // Most recent sample time (stop time when inactive)
 | 
			
		||||
	sRate  time.Duration // Sampling rate
 | 
			
		||||
 | 
			
		||||
	tBytes int64         // Number of bytes expected in the current transfer
 | 
			
		||||
	tLast  time.Duration // Time of the most recent transfer of at least 1 byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new flow control monitor. Instantaneous transfer rate is
 | 
			
		||||
// measured and updated for each sampleRate interval. windowSize determines the
 | 
			
		||||
// weight of each sample in the exponential moving average (EMA) calculation.
 | 
			
		||||
// The exact formulas are:
 | 
			
		||||
//
 | 
			
		||||
// 	sampleTime = currentTime - prevSampleTime
 | 
			
		||||
// 	sampleRate = byteCount / sampleTime
 | 
			
		||||
// 	weight     = 1 - exp(-sampleTime/windowSize)
 | 
			
		||||
// 	newRate    = weight*sampleRate + (1-weight)*oldRate
 | 
			
		||||
//
 | 
			
		||||
// The default values for sampleRate and windowSize (if <= 0) are 100ms and 1s,
 | 
			
		||||
// respectively.
 | 
			
		||||
func New(sampleRate, windowSize time.Duration) *Monitor {
 | 
			
		||||
	if sampleRate = clockRound(sampleRate); sampleRate <= 0 {
 | 
			
		||||
		sampleRate = 5 * clockRate
 | 
			
		||||
	}
 | 
			
		||||
	if windowSize <= 0 {
 | 
			
		||||
		windowSize = 1 * time.Second
 | 
			
		||||
	}
 | 
			
		||||
	now := clock()
 | 
			
		||||
	return &Monitor{
 | 
			
		||||
		active:  true,
 | 
			
		||||
		start:   now,
 | 
			
		||||
		rWindow: windowSize.Seconds(),
 | 
			
		||||
		sLast:   now,
 | 
			
		||||
		sRate:   sampleRate,
 | 
			
		||||
		tLast:   now,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update records the transfer of n bytes and returns n. It should be called
 | 
			
		||||
// after each Read/Write operation, even if n is 0.
 | 
			
		||||
func (m *Monitor) Update(n int) int {
 | 
			
		||||
	m.mu.Lock()
 | 
			
		||||
	m.update(n)
 | 
			
		||||
	m.mu.Unlock()
 | 
			
		||||
	return n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IO is a convenience method intended to wrap io.Reader and io.Writer method
 | 
			
		||||
// execution. It calls m.Update(n) and then returns (n, err) unmodified.
 | 
			
		||||
func (m *Monitor) IO(n int, err error) (int, error) {
 | 
			
		||||
	return m.Update(n), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Done marks the transfer as finished and prevents any further updates or
 | 
			
		||||
// limiting. Instantaneous and current transfer rates drop to 0. Update, IO, and
 | 
			
		||||
// Limit methods become NOOPs. It returns the total number of bytes transferred.
 | 
			
		||||
func (m *Monitor) Done() int64 {
 | 
			
		||||
	m.mu.Lock()
 | 
			
		||||
	if now := m.update(0); m.sBytes > 0 {
 | 
			
		||||
		m.reset(now)
 | 
			
		||||
	}
 | 
			
		||||
	m.active = false
 | 
			
		||||
	m.tLast = 0
 | 
			
		||||
	n := m.bytes
 | 
			
		||||
	m.mu.Unlock()
 | 
			
		||||
	return n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// timeRemLimit is the maximum Status.TimeRem value.
 | 
			
		||||
const timeRemLimit = 999*time.Hour + 59*time.Minute + 59*time.Second
 | 
			
		||||
 | 
			
		||||
// Status represents the current Monitor status. All transfer rates are in bytes
 | 
			
		||||
// per second rounded to the nearest byte.
 | 
			
		||||
type Status struct {
 | 
			
		||||
	Active   bool          // Flag indicating an active transfer
 | 
			
		||||
	Start    time.Time     // Transfer start time
 | 
			
		||||
	Duration time.Duration // Time period covered by the statistics
 | 
			
		||||
	Idle     time.Duration // Time since the last transfer of at least 1 byte
 | 
			
		||||
	Bytes    int64         // Total number of bytes transferred
 | 
			
		||||
	Samples  int64         // Total number of samples taken
 | 
			
		||||
	InstRate int64         // Instantaneous transfer rate
 | 
			
		||||
	CurRate  int64         // Current transfer rate (EMA of InstRate)
 | 
			
		||||
	AvgRate  int64         // Average transfer rate (Bytes / Duration)
 | 
			
		||||
	PeakRate int64         // Maximum instantaneous transfer rate
 | 
			
		||||
	BytesRem int64         // Number of bytes remaining in the transfer
 | 
			
		||||
	TimeRem  time.Duration // Estimated time to completion
 | 
			
		||||
	Progress Percent       // Overall transfer progress
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Status returns current transfer status information. The returned value
 | 
			
		||||
// becomes static after a call to Done.
 | 
			
		||||
func (m *Monitor) Status() Status {
 | 
			
		||||
	m.mu.Lock()
 | 
			
		||||
	now := m.update(0)
 | 
			
		||||
	s := Status{
 | 
			
		||||
		Active:   m.active,
 | 
			
		||||
		Start:    clockToTime(m.start),
 | 
			
		||||
		Duration: m.sLast - m.start,
 | 
			
		||||
		Idle:     now - m.tLast,
 | 
			
		||||
		Bytes:    m.bytes,
 | 
			
		||||
		Samples:  m.samples,
 | 
			
		||||
		PeakRate: round(m.rPeak),
 | 
			
		||||
		BytesRem: m.tBytes - m.bytes,
 | 
			
		||||
		Progress: percentOf(float64(m.bytes), float64(m.tBytes)),
 | 
			
		||||
	}
 | 
			
		||||
	if s.BytesRem < 0 {
 | 
			
		||||
		s.BytesRem = 0
 | 
			
		||||
	}
 | 
			
		||||
	if s.Duration > 0 {
 | 
			
		||||
		rAvg := float64(s.Bytes) / s.Duration.Seconds()
 | 
			
		||||
		s.AvgRate = round(rAvg)
 | 
			
		||||
		if s.Active {
 | 
			
		||||
			s.InstRate = round(m.rSample)
 | 
			
		||||
			s.CurRate = round(m.rEMA)
 | 
			
		||||
			if s.BytesRem > 0 {
 | 
			
		||||
				if tRate := 0.8*m.rEMA + 0.2*rAvg; tRate > 0 {
 | 
			
		||||
					ns := float64(s.BytesRem) / tRate * 1e9
 | 
			
		||||
					if ns > float64(timeRemLimit) {
 | 
			
		||||
						ns = float64(timeRemLimit)
 | 
			
		||||
					}
 | 
			
		||||
					s.TimeRem = clockRound(time.Duration(ns))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	m.mu.Unlock()
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Limit restricts the instantaneous (per-sample) data flow to rate bytes per
 | 
			
		||||
// second. It returns the maximum number of bytes (0 <= n <= want) that may be
 | 
			
		||||
// transferred immediately without exceeding the limit. If block == true, the
 | 
			
		||||
// call blocks until n > 0. want is returned unmodified if want < 1, rate < 1,
 | 
			
		||||
// or the transfer is inactive (after a call to Done).
 | 
			
		||||
//
 | 
			
		||||
// At least one byte is always allowed to be transferred in any given sampling
 | 
			
		||||
// period. Thus, if the sampling rate is 100ms, the lowest achievable flow rate
 | 
			
		||||
// is 10 bytes per second.
 | 
			
		||||
//
 | 
			
		||||
// For usage examples, see the implementation of Reader and Writer in io.go.
 | 
			
		||||
func (m *Monitor) Limit(want int, rate int64, block bool) (n int) {
 | 
			
		||||
	if want < 1 || rate < 1 {
 | 
			
		||||
		return want
 | 
			
		||||
	}
 | 
			
		||||
	m.mu.Lock()
 | 
			
		||||
 | 
			
		||||
	// Determine the maximum number of bytes that can be sent in one sample
 | 
			
		||||
	limit := round(float64(rate) * m.sRate.Seconds())
 | 
			
		||||
	if limit <= 0 {
 | 
			
		||||
		limit = 1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If block == true, wait until m.sBytes < limit
 | 
			
		||||
	if now := m.update(0); block {
 | 
			
		||||
		for m.sBytes >= limit && m.active {
 | 
			
		||||
			now = m.waitNextSample(now)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make limit <= want (unlimited if the transfer is no longer active)
 | 
			
		||||
	if limit -= m.sBytes; limit > int64(want) || !m.active {
 | 
			
		||||
		limit = int64(want)
 | 
			
		||||
	}
 | 
			
		||||
	m.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	if limit < 0 {
 | 
			
		||||
		limit = 0
 | 
			
		||||
	}
 | 
			
		||||
	return int(limit)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetTransferSize specifies the total size of the data transfer, which allows
 | 
			
		||||
// the Monitor to calculate the overall progress and time to completion.
 | 
			
		||||
func (m *Monitor) SetTransferSize(bytes int64) {
 | 
			
		||||
	if bytes < 0 {
 | 
			
		||||
		bytes = 0
 | 
			
		||||
	}
 | 
			
		||||
	m.mu.Lock()
 | 
			
		||||
	m.tBytes = bytes
 | 
			
		||||
	m.mu.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// update accumulates the transferred byte count for the current sample until
 | 
			
		||||
// clock() - m.sLast >= m.sRate. The monitor status is updated once the current
 | 
			
		||||
// sample is done.
 | 
			
		||||
func (m *Monitor) update(n int) (now time.Duration) {
 | 
			
		||||
	if !m.active {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if now = clock(); n > 0 {
 | 
			
		||||
		m.tLast = now
 | 
			
		||||
	}
 | 
			
		||||
	m.sBytes += int64(n)
 | 
			
		||||
	if sTime := now - m.sLast; sTime >= m.sRate {
 | 
			
		||||
		t := sTime.Seconds()
 | 
			
		||||
		if m.rSample = float64(m.sBytes) / t; m.rSample > m.rPeak {
 | 
			
		||||
			m.rPeak = m.rSample
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Exponential moving average using a method similar to *nix load
 | 
			
		||||
		// average calculation. Longer sampling periods carry greater weight.
 | 
			
		||||
		if m.samples > 0 {
 | 
			
		||||
			w := math.Exp(-t / m.rWindow)
 | 
			
		||||
			m.rEMA = m.rSample + w*(m.rEMA-m.rSample)
 | 
			
		||||
		} else {
 | 
			
		||||
			m.rEMA = m.rSample
 | 
			
		||||
		}
 | 
			
		||||
		m.reset(now)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reset clears the current sample state in preparation for the next sample.
 | 
			
		||||
func (m *Monitor) reset(sampleTime time.Duration) {
 | 
			
		||||
	m.bytes += m.sBytes
 | 
			
		||||
	m.samples++
 | 
			
		||||
	m.sBytes = 0
 | 
			
		||||
	m.sLast = sampleTime
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// waitNextSample sleeps for the remainder of the current sample. The lock is
 | 
			
		||||
// released and reacquired during the actual sleep period, so it's possible for
 | 
			
		||||
// the transfer to be inactive when this method returns.
 | 
			
		||||
func (m *Monitor) waitNextSample(now time.Duration) time.Duration {
 | 
			
		||||
	const minWait = 5 * time.Millisecond
 | 
			
		||||
	current := m.sLast
 | 
			
		||||
 | 
			
		||||
	// sleep until the last sample time changes (ideally, just one iteration)
 | 
			
		||||
	for m.sLast == current && m.active {
 | 
			
		||||
		d := current + m.sRate - now
 | 
			
		||||
		m.mu.Unlock()
 | 
			
		||||
		if d < minWait {
 | 
			
		||||
			d = minWait
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(d)
 | 
			
		||||
		m.mu.Lock()
 | 
			
		||||
		now = m.update(0)
 | 
			
		||||
	}
 | 
			
		||||
	return now
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/io.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/io.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
			
		||||
//
 | 
			
		||||
// Written by Maxim Khitrov (November 2012)
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package flowrate
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrLimit is returned by the Writer when a non-blocking write is short due to
 | 
			
		||||
// the transfer rate limit.
 | 
			
		||||
var ErrLimit = errors.New("flowrate: flow rate limit exceeded")
 | 
			
		||||
 | 
			
		||||
// Limiter is implemented by the Reader and Writer to provide a consistent
 | 
			
		||||
// interface for monitoring and controlling data transfer.
 | 
			
		||||
type Limiter interface {
 | 
			
		||||
	Done() int64
 | 
			
		||||
	Status() Status
 | 
			
		||||
	SetTransferSize(bytes int64)
 | 
			
		||||
	SetLimit(new int64) (old int64)
 | 
			
		||||
	SetBlocking(new bool) (old bool)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reader implements io.ReadCloser with a restriction on the rate of data
 | 
			
		||||
// transfer.
 | 
			
		||||
type Reader struct {
 | 
			
		||||
	io.Reader // Data source
 | 
			
		||||
	*Monitor  // Flow control monitor
 | 
			
		||||
 | 
			
		||||
	limit int64 // Rate limit in bytes per second (unlimited when <= 0)
 | 
			
		||||
	block bool  // What to do when no new bytes can be read due to the limit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewReader restricts all Read operations on r to limit bytes per second.
 | 
			
		||||
func NewReader(r io.Reader, limit int64) *Reader {
 | 
			
		||||
	return &Reader{r, New(0, 0), limit, true}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Read reads up to len(p) bytes into p without exceeding the current transfer
 | 
			
		||||
// rate limit. It returns (0, nil) immediately if r is non-blocking and no new
 | 
			
		||||
// bytes can be read at this time.
 | 
			
		||||
func (r *Reader) Read(p []byte) (n int, err error) {
 | 
			
		||||
	p = p[:r.Limit(len(p), r.limit, r.block)]
 | 
			
		||||
	if len(p) > 0 {
 | 
			
		||||
		n, err = r.IO(r.Reader.Read(p))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetLimit changes the transfer rate limit to new bytes per second and returns
 | 
			
		||||
// the previous setting.
 | 
			
		||||
func (r *Reader) SetLimit(new int64) (old int64) {
 | 
			
		||||
	old, r.limit = r.limit, new
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetBlocking changes the blocking behavior and returns the previous setting. A
 | 
			
		||||
// Read call on a non-blocking reader returns immediately if no additional bytes
 | 
			
		||||
// may be read at this time due to the rate limit.
 | 
			
		||||
func (r *Reader) SetBlocking(new bool) (old bool) {
 | 
			
		||||
	old, r.block = r.block, new
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the underlying reader if it implements the io.Closer interface.
 | 
			
		||||
func (r *Reader) Close() error {
 | 
			
		||||
	defer r.Done()
 | 
			
		||||
	if c, ok := r.Reader.(io.Closer); ok {
 | 
			
		||||
		return c.Close()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Writer implements io.WriteCloser with a restriction on the rate of data
 | 
			
		||||
// transfer.
 | 
			
		||||
type Writer struct {
 | 
			
		||||
	io.Writer // Data destination
 | 
			
		||||
	*Monitor  // Flow control monitor
 | 
			
		||||
 | 
			
		||||
	limit int64 // Rate limit in bytes per second (unlimited when <= 0)
 | 
			
		||||
	block bool  // What to do when no new bytes can be written due to the limit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewWriter restricts all Write operations on w to limit bytes per second. The
 | 
			
		||||
// transfer rate and the default blocking behavior (true) can be changed
 | 
			
		||||
// directly on the returned *Writer.
 | 
			
		||||
func NewWriter(w io.Writer, limit int64) *Writer {
 | 
			
		||||
	return &Writer{w, New(0, 0), limit, true}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write writes len(p) bytes from p to the underlying data stream without
 | 
			
		||||
// exceeding the current transfer rate limit. It returns (n, ErrLimit) if w is
 | 
			
		||||
// non-blocking and no additional bytes can be written at this time.
 | 
			
		||||
func (w *Writer) Write(p []byte) (n int, err error) {
 | 
			
		||||
	var c int
 | 
			
		||||
	for len(p) > 0 && err == nil {
 | 
			
		||||
		s := p[:w.Limit(len(p), w.limit, w.block)]
 | 
			
		||||
		if len(s) > 0 {
 | 
			
		||||
			c, err = w.IO(w.Writer.Write(s))
 | 
			
		||||
		} else {
 | 
			
		||||
			return n, ErrLimit
 | 
			
		||||
		}
 | 
			
		||||
		p = p[c:]
 | 
			
		||||
		n += c
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetLimit changes the transfer rate limit to new bytes per second and returns
 | 
			
		||||
// the previous setting.
 | 
			
		||||
func (w *Writer) SetLimit(new int64) (old int64) {
 | 
			
		||||
	old, w.limit = w.limit, new
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetBlocking changes the blocking behavior and returns the previous setting. A
 | 
			
		||||
// Write call on a non-blocking writer returns as soon as no additional bytes
 | 
			
		||||
// may be written at this time due to the rate limit.
 | 
			
		||||
func (w *Writer) SetBlocking(new bool) (old bool) {
 | 
			
		||||
	old, w.block = w.block, new
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the underlying writer if it implements the io.Closer interface.
 | 
			
		||||
func (w *Writer) Close() error {
 | 
			
		||||
	defer w.Done()
 | 
			
		||||
	if c, ok := w.Writer.(io.Closer); ok {
 | 
			
		||||
		return c.Close()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/io_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/io_test.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,146 @@
 | 
			
		||||
//
 | 
			
		||||
// Written by Maxim Khitrov (November 2012)
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package flowrate
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	_50ms  = 50 * time.Millisecond
 | 
			
		||||
	_100ms = 100 * time.Millisecond
 | 
			
		||||
	_200ms = 200 * time.Millisecond
 | 
			
		||||
	_300ms = 300 * time.Millisecond
 | 
			
		||||
	_400ms = 400 * time.Millisecond
 | 
			
		||||
	_500ms = 500 * time.Millisecond
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func nextStatus(m *Monitor) Status {
 | 
			
		||||
	samples := m.samples
 | 
			
		||||
	for i := 0; i < 30; i++ {
 | 
			
		||||
		if s := m.Status(); s.Samples != samples {
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(5 * time.Millisecond)
 | 
			
		||||
	}
 | 
			
		||||
	return m.Status()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestReader(t *testing.T) {
 | 
			
		||||
	in := make([]byte, 100)
 | 
			
		||||
	for i := range in {
 | 
			
		||||
		in[i] = byte(i)
 | 
			
		||||
	}
 | 
			
		||||
	b := make([]byte, 100)
 | 
			
		||||
	r := NewReader(bytes.NewReader(in), 100)
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// Make sure r implements Limiter
 | 
			
		||||
	_ = Limiter(r)
 | 
			
		||||
 | 
			
		||||
	// 1st read of 10 bytes is performed immediately
 | 
			
		||||
	if n, err := r.Read(b); n != 10 || err != nil {
 | 
			
		||||
		t.Fatalf("r.Read(b) expected 10 (<nil>); got %v (%v)", n, err)
 | 
			
		||||
	} else if rt := time.Since(start); rt > _50ms {
 | 
			
		||||
		t.Fatalf("r.Read(b) took too long (%v)", rt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No new Reads allowed in the current sample
 | 
			
		||||
	r.SetBlocking(false)
 | 
			
		||||
	if n, err := r.Read(b); n != 0 || err != nil {
 | 
			
		||||
		t.Fatalf("r.Read(b) expected 0 (<nil>); got %v (%v)", n, err)
 | 
			
		||||
	} else if rt := time.Since(start); rt > _50ms {
 | 
			
		||||
		t.Fatalf("r.Read(b) took too long (%v)", rt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	status := [6]Status{0: r.Status()} // No samples in the first status
 | 
			
		||||
 | 
			
		||||
	// 2nd read of 10 bytes blocks until the next sample
 | 
			
		||||
	r.SetBlocking(true)
 | 
			
		||||
	if n, err := r.Read(b[10:]); n != 10 || err != nil {
 | 
			
		||||
		t.Fatalf("r.Read(b[10:]) expected 10 (<nil>); got %v (%v)", n, err)
 | 
			
		||||
	} else if rt := time.Since(start); rt < _100ms {
 | 
			
		||||
		t.Fatalf("r.Read(b[10:]) returned ahead of time (%v)", rt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	status[1] = r.Status()            // 1st sample
 | 
			
		||||
	status[2] = nextStatus(r.Monitor) // 2nd sample
 | 
			
		||||
	status[3] = nextStatus(r.Monitor) // No activity for the 3rd sample
 | 
			
		||||
 | 
			
		||||
	if n := r.Done(); n != 20 {
 | 
			
		||||
		t.Fatalf("r.Done() expected 20; got %v", n)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	status[4] = r.Status()
 | 
			
		||||
	status[5] = nextStatus(r.Monitor) // Timeout
 | 
			
		||||
	start = status[0].Start
 | 
			
		||||
 | 
			
		||||
	// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress
 | 
			
		||||
	want := []Status{
 | 
			
		||||
		Status{true, start, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
 | 
			
		||||
		Status{true, start, _100ms, 0, 10, 1, 100, 100, 100, 100, 0, 0, 0},
 | 
			
		||||
		Status{true, start, _200ms, _100ms, 20, 2, 100, 100, 100, 100, 0, 0, 0},
 | 
			
		||||
		Status{true, start, _300ms, _200ms, 20, 3, 0, 90, 67, 100, 0, 0, 0},
 | 
			
		||||
		Status{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},
 | 
			
		||||
		Status{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0},
 | 
			
		||||
	}
 | 
			
		||||
	for i, s := range status {
 | 
			
		||||
		if !reflect.DeepEqual(&s, &want[i]) {
 | 
			
		||||
			t.Errorf("r.Status(%v) expected %v; got %v", i, want[i], s)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !bytes.Equal(b[:20], in[:20]) {
 | 
			
		||||
		t.Errorf("r.Read() input doesn't match output")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWriter(t *testing.T) {
 | 
			
		||||
	b := make([]byte, 100)
 | 
			
		||||
	for i := range b {
 | 
			
		||||
		b[i] = byte(i)
 | 
			
		||||
	}
 | 
			
		||||
	w := NewWriter(&bytes.Buffer{}, 200)
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// Make sure w implements Limiter
 | 
			
		||||
	_ = Limiter(w)
 | 
			
		||||
 | 
			
		||||
	// Non-blocking 20-byte write for the first sample returns ErrLimit
 | 
			
		||||
	w.SetBlocking(false)
 | 
			
		||||
	if n, err := w.Write(b); n != 20 || err != ErrLimit {
 | 
			
		||||
		t.Fatalf("w.Write(b) expected 20 (ErrLimit); got %v (%v)", n, err)
 | 
			
		||||
	} else if rt := time.Since(start); rt > _50ms {
 | 
			
		||||
		t.Fatalf("w.Write(b) took too long (%v)", rt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Blocking 80-byte write
 | 
			
		||||
	w.SetBlocking(true)
 | 
			
		||||
	if n, err := w.Write(b[20:]); n != 80 || err != nil {
 | 
			
		||||
		t.Fatalf("w.Write(b[20:]) expected 80 (<nil>); got %v (%v)", n, err)
 | 
			
		||||
	} else if rt := time.Since(start); rt < _400ms {
 | 
			
		||||
		t.Fatalf("w.Write(b[20:]) returned ahead of time (%v)", rt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.SetTransferSize(100)
 | 
			
		||||
	status := []Status{w.Status(), nextStatus(w.Monitor)}
 | 
			
		||||
	start = status[0].Start
 | 
			
		||||
 | 
			
		||||
	// Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress
 | 
			
		||||
	want := []Status{
 | 
			
		||||
		Status{true, start, _400ms, 0, 80, 4, 200, 200, 200, 200, 20, _100ms, 80000},
 | 
			
		||||
		Status{true, start, _500ms, _100ms, 100, 5, 200, 200, 200, 200, 0, 0, 100000},
 | 
			
		||||
	}
 | 
			
		||||
	for i, s := range status {
 | 
			
		||||
		if !reflect.DeepEqual(&s, &want[i]) {
 | 
			
		||||
			t.Errorf("w.Status(%v) expected %v; got %v", i, want[i], s)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !bytes.Equal(b, w.Writer.(*bytes.Buffer).Bytes()) {
 | 
			
		||||
		t.Errorf("w.Write() input doesn't match output")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Godeps/_workspace/src/github.com/mxk/go-flowrate/flowrate/util.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
//
 | 
			
		||||
// Written by Maxim Khitrov (November 2012)
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package flowrate
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// clockRate is the resolution and precision of clock().
 | 
			
		||||
const clockRate = 20 * time.Millisecond
 | 
			
		||||
 | 
			
		||||
// czero is the process start time rounded down to the nearest clockRate
 | 
			
		||||
// increment.
 | 
			
		||||
var czero = time.Duration(time.Now().UnixNano()) / clockRate * clockRate
 | 
			
		||||
 | 
			
		||||
// clock returns a low resolution timestamp relative to the process start time.
 | 
			
		||||
func clock() time.Duration {
 | 
			
		||||
	return time.Duration(time.Now().UnixNano())/clockRate*clockRate - czero
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// clockToTime converts a clock() timestamp to an absolute time.Time value.
 | 
			
		||||
func clockToTime(c time.Duration) time.Time {
 | 
			
		||||
	return time.Unix(0, int64(czero+c))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// clockRound returns d rounded to the nearest clockRate increment.
 | 
			
		||||
func clockRound(d time.Duration) time.Duration {
 | 
			
		||||
	return (d + clockRate>>1) / clockRate * clockRate
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// round returns x rounded to the nearest int64 (non-negative values only).
 | 
			
		||||
func round(x float64) int64 {
 | 
			
		||||
	if _, frac := math.Modf(x); frac >= 0.5 {
 | 
			
		||||
		return int64(math.Ceil(x))
 | 
			
		||||
	}
 | 
			
		||||
	return int64(math.Floor(x))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Percent represents a percentage in increments of 1/1000th of a percent.
 | 
			
		||||
type Percent uint32
 | 
			
		||||
 | 
			
		||||
// percentOf calculates what percent of the total is x.
 | 
			
		||||
func percentOf(x, total float64) Percent {
 | 
			
		||||
	if x < 0 || total <= 0 {
 | 
			
		||||
		return 0
 | 
			
		||||
	} else if p := round(x / total * 1e5); p <= math.MaxUint32 {
 | 
			
		||||
		return Percent(p)
 | 
			
		||||
	}
 | 
			
		||||
	return Percent(math.MaxUint32)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p Percent) Float() float64 {
 | 
			
		||||
	return float64(p) * 1e-3
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p Percent) String() string {
 | 
			
		||||
	var buf [12]byte
 | 
			
		||||
	b := strconv.AppendUint(buf[:0], uint64(p)/1000, 10)
 | 
			
		||||
	n := len(b)
 | 
			
		||||
	b = strconv.AppendUint(b, 1000+uint64(p)%1000, 10)
 | 
			
		||||
	b[n] = '.'
 | 
			
		||||
	return string(append(b, '%'))
 | 
			
		||||
}
 | 
			
		||||
@@ -5706,6 +5706,54 @@
 | 
			
		||||
      "summary": "connect GET requests to exec of Pod",
 | 
			
		||||
      "nickname": "connectGetNamespacedPodExec",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "stdin",
 | 
			
		||||
        "description": "redirect the standard input stream of the pod for this call; defaults to false",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "stdout",
 | 
			
		||||
        "description": "redirect the standard output stream of the pod for this call; defaults to true",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "stderr",
 | 
			
		||||
        "description": "redirect the standard error stream of the pod for this call; defaults to true",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "tty",
 | 
			
		||||
        "description": "allocate a terminal for this exec call; defaults to false",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "container",
 | 
			
		||||
        "description": "the container in which to execute the command. Defaults to only container if there is only one container in the pod.",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "command",
 | 
			
		||||
        "description": "the command to execute; argv array; not executed within a shell",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -5736,6 +5784,54 @@
 | 
			
		||||
      "summary": "connect POST requests to exec of Pod",
 | 
			
		||||
      "nickname": "connectPostNamespacedPodExec",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "stdin",
 | 
			
		||||
        "description": "redirect the standard input stream of the pod for this call; defaults to false",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "stdout",
 | 
			
		||||
        "description": "redirect the standard output stream of the pod for this call; defaults to true",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "stderr",
 | 
			
		||||
        "description": "redirect the standard error stream of the pod for this call; defaults to true",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "tty",
 | 
			
		||||
        "description": "allocate a terminal for this exec call; defaults to false",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "container",
 | 
			
		||||
        "description": "the container in which to execute the command. Defaults to only container if there is only one container in the pod.",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "command",
 | 
			
		||||
        "description": "the command to execute; argv array; not executed within a shell",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -5780,6 +5876,30 @@
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "container",
 | 
			
		||||
        "description": "the container for which to stream logs; defaults to only container if there is one container in the pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "follow",
 | 
			
		||||
        "description": "follow the log stream of the pod; defaults to false",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "boolean",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "previous",
 | 
			
		||||
        "description": "return previous terminated container logs; defaults to false",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -5889,6 +6009,14 @@
 | 
			
		||||
      "summary": "connect GET requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectGetNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -5919,6 +6047,14 @@
 | 
			
		||||
      "summary": "connect POST requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectPostNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -5949,6 +6085,14 @@
 | 
			
		||||
      "summary": "connect PUT requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectPutNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -5979,6 +6123,14 @@
 | 
			
		||||
      "summary": "connect DELETE requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectDeleteNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6009,6 +6161,14 @@
 | 
			
		||||
      "summary": "connect HEAD requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectHeadNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6039,6 +6199,14 @@
 | 
			
		||||
      "summary": "connect OPTIONS requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectOptionsNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6075,6 +6243,14 @@
 | 
			
		||||
      "summary": "connect GET requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectGetNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6113,6 +6289,14 @@
 | 
			
		||||
      "summary": "connect POST requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectPostNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6151,6 +6335,14 @@
 | 
			
		||||
      "summary": "connect PUT requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectPutNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6189,6 +6381,14 @@
 | 
			
		||||
      "summary": "connect DELETE requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectDeleteNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6227,6 +6427,14 @@
 | 
			
		||||
      "summary": "connect HEAD requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectHeadNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
@@ -6265,6 +6473,14 @@
 | 
			
		||||
      "summary": "connect OPTIONS requests to proxy of Pod",
 | 
			
		||||
      "nickname": "connectOptionsNamespacedPodProxy",
 | 
			
		||||
      "parameters": [
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "query",
 | 
			
		||||
        "name": "path",
 | 
			
		||||
        "description": "URL path to use in proxy request to pod",
 | 
			
		||||
        "required": false,
 | 
			
		||||
        "allowMultiple": false
 | 
			
		||||
       },
 | 
			
		||||
       {
 | 
			
		||||
        "type": "string",
 | 
			
		||||
        "paramType": "path",
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ the system will bring them back to the original state, in particular:
 | 
			
		||||
 | 
			
		||||
On the cluster, the add-ons are kept in ```/etc/kubernetes/addons``` on the master node, in yaml files
 | 
			
		||||
(json is not supported at the moment). A system daemon periodically checks if
 | 
			
		||||
the contents of this directory is consistent with the add-one objects on the API
 | 
			
		||||
the contents of this directory is consistent with the add-on objects on the API
 | 
			
		||||
server. If any difference is spotted, the system updates the API objects
 | 
			
		||||
accordingly. (Limitation: for now, the system compares only the names of objects
 | 
			
		||||
in the directory and on the API server. So changes in parameters may not be
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: ReplicationController
 | 
			
		||||
metadata:
 | 
			
		||||
  name: monitoring-heapster-v5
 | 
			
		||||
  name: monitoring-heapster-v6
 | 
			
		||||
  namespace: kube-system
 | 
			
		||||
  labels:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
    kubernetes.io/cluster-service: "true"
 | 
			
		||||
spec:
 | 
			
		||||
  replicas: 1
 | 
			
		||||
  selector:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
  template:
 | 
			
		||||
    metadata:
 | 
			
		||||
      labels:
 | 
			
		||||
        k8s-app: heapster
 | 
			
		||||
        version: v5
 | 
			
		||||
        version: v6
 | 
			
		||||
        kubernetes.io/cluster-service: "true"
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.16.0
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.17.0
 | 
			
		||||
          name: heapster
 | 
			
		||||
          resources:
 | 
			
		||||
            limits:
 | 
			
		||||
@@ -33,11 +33,3 @@ spec:
 | 
			
		||||
            - --sink=gcl
 | 
			
		||||
            - --poll_duration=2m
 | 
			
		||||
            - --stats_resolution=1m
 | 
			
		||||
          volumeMounts: 
 | 
			
		||||
            - name: ssl-certs
 | 
			
		||||
              mountPath: /etc/ssl/certs
 | 
			
		||||
              readOnly: true
 | 
			
		||||
      volumes: 
 | 
			
		||||
        - name: ssl-certs
 | 
			
		||||
          hostPath: 
 | 
			
		||||
            path: "/etc/ssl/certs"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: ReplicationController
 | 
			
		||||
metadata:
 | 
			
		||||
  name: monitoring-heapster-v5
 | 
			
		||||
  name: monitoring-heapster-v6
 | 
			
		||||
  namespace: kube-system
 | 
			
		||||
  labels:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
    kubernetes.io/cluster-service: "true"
 | 
			
		||||
spec:
 | 
			
		||||
  replicas: 1
 | 
			
		||||
  selector:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
  template:
 | 
			
		||||
    metadata:
 | 
			
		||||
      labels:
 | 
			
		||||
        k8s-app: heapster
 | 
			
		||||
        version: v5
 | 
			
		||||
        version: v6
 | 
			
		||||
        kubernetes.io/cluster-service: "true"
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.16.0
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.17.0
 | 
			
		||||
          name: heapster
 | 
			
		||||
          resources:
 | 
			
		||||
            limits:
 | 
			
		||||
@@ -33,11 +33,3 @@ spec:
 | 
			
		||||
            - --sink=influxdb:http://monitoring-influxdb:8086
 | 
			
		||||
            - --poll_duration=2m
 | 
			
		||||
            - --stats_resolution=1m
 | 
			
		||||
          volumeMounts: 
 | 
			
		||||
            - name: ssl-certs
 | 
			
		||||
              mountPath: /etc/ssl/certs
 | 
			
		||||
              readOnly: true
 | 
			
		||||
      volumes: 
 | 
			
		||||
        - name: ssl-certs
 | 
			
		||||
          hostPath: 
 | 
			
		||||
            path: "/etc/ssl/certs"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: ReplicationController
 | 
			
		||||
metadata:
 | 
			
		||||
  name: monitoring-heapster-v5
 | 
			
		||||
  name: monitoring-heapster-v6
 | 
			
		||||
  namespace: kube-system
 | 
			
		||||
  labels:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
    kubernetes.io/cluster-service: "true"
 | 
			
		||||
spec:
 | 
			
		||||
  replicas: 1
 | 
			
		||||
  selector:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
  template:
 | 
			
		||||
    metadata:
 | 
			
		||||
      labels:
 | 
			
		||||
        k8s-app: heapster
 | 
			
		||||
        version: v5
 | 
			
		||||
        version: v6
 | 
			
		||||
        kubernetes.io/cluster-service: "true"
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.16.0
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.17.0
 | 
			
		||||
          name: heapster
 | 
			
		||||
          resources:
 | 
			
		||||
            limits:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,26 @@
 | 
			
		||||
apiVersion: v1
 | 
			
		||||
kind: ReplicationController
 | 
			
		||||
metadata:
 | 
			
		||||
  name: monitoring-heapster-v5
 | 
			
		||||
  name: monitoring-heapster-v6
 | 
			
		||||
  namespace: kube-system
 | 
			
		||||
  labels:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
    kubernetes.io/cluster-service: "true"
 | 
			
		||||
spec:
 | 
			
		||||
  replicas: 1
 | 
			
		||||
  selector:
 | 
			
		||||
    k8s-app: heapster
 | 
			
		||||
    version: v5
 | 
			
		||||
    version: v6
 | 
			
		||||
  template:
 | 
			
		||||
    metadata:
 | 
			
		||||
      labels:
 | 
			
		||||
        k8s-app: heapster
 | 
			
		||||
        version: v5
 | 
			
		||||
        version: v6
 | 
			
		||||
        kubernetes.io/cluster-service: "true"
 | 
			
		||||
    spec:
 | 
			
		||||
      containers:
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.16.0
 | 
			
		||||
        - image: gcr.io/google_containers/heapster:v0.17.0
 | 
			
		||||
          name: heapster
 | 
			
		||||
          resources:
 | 
			
		||||
            limits:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
# build the hyperkube image.
 | 
			
		||||
 | 
			
		||||
VERSION=v0.18.2
 | 
			
		||||
VERSION=v1.0.1
 | 
			
		||||
 | 
			
		||||
all:
 | 
			
		||||
	cp ../../saltbase/salt/helpers/safe_format_and_mount .
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,8 @@ export FLANNEL_OPTS=${FLANNEL_OPTS:-"Network": 172.16.0.0/16}
 | 
			
		||||
# Admission Controllers to invoke prior to persisting objects in cluster
 | 
			
		||||
export ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,ServiceAccount,ResourceQuota,SecurityContextDeny
 | 
			
		||||
 | 
			
		||||
SERVICE_NODE_PORT_RANGE=${SERVICE_NODE_PORT_RANGE:-"30000-32767"}
 | 
			
		||||
 | 
			
		||||
# Optional: Enable node logging.
 | 
			
		||||
ENABLE_NODE_LOGGING=false
 | 
			
		||||
LOGGING_DESTINATION=${LOGGING_DESTINATION:-elasticsearch}
 | 
			
		||||
 
 | 
			
		||||
@@ -202,6 +202,7 @@ KUBE_APISERVER_OPTS="--insecure-bind-address=0.0.0.0 \
 | 
			
		||||
--logtostderr=true \
 | 
			
		||||
--service-cluster-ip-range=${1} \
 | 
			
		||||
--admission-control=${2} \
 | 
			
		||||
--service-node-port-range=${3} \
 | 
			
		||||
--client-ca-file=/srv/kubernetes/ca.crt \
 | 
			
		||||
--tls-cert-file=/srv/kubernetes/server.cert \
 | 
			
		||||
--tls-private-key-file=/srv/kubernetes/server.key"
 | 
			
		||||
@@ -371,7 +372,7 @@ function provision-master() {
 | 
			
		||||
                            ~/kube/make-ca-cert ${MASTER_IP} IP:${MASTER_IP},IP:${SERVICE_CLUSTER_IP_RANGE%.*}.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local; \
 | 
			
		||||
                            setClusterInfo; \
 | 
			
		||||
                            create-etcd-opts "${mm[${MASTER_IP}]}" "${MASTER_IP}" "${CLUSTER}"; \
 | 
			
		||||
                            create-kube-apiserver-opts "${SERVICE_CLUSTER_IP_RANGE}" "${ADMISSION_CONTROL}"; \
 | 
			
		||||
                            create-kube-apiserver-opts "${SERVICE_CLUSTER_IP_RANGE}" "${ADMISSION_CONTROL}" "${SERVICE_NODE_PORT_RANGE}"; \
 | 
			
		||||
                            create-kube-controller-manager-opts "${MINION_IPS}"; \
 | 
			
		||||
                            create-kube-scheduler-opts; \
 | 
			
		||||
                            create-flanneld-opts; \
 | 
			
		||||
@@ -413,7 +414,7 @@ function provision-masterandminion() {
 | 
			
		||||
  ssh $SSH_OPTS -t $MASTER "source ~/kube/util.sh; \
 | 
			
		||||
                            setClusterInfo; \
 | 
			
		||||
                            create-etcd-opts "${mm[${MASTER_IP}]}" "${MASTER_IP}" "${CLUSTER}"; \
 | 
			
		||||
                            create-kube-apiserver-opts "${SERVICE_CLUSTER_IP_RANGE}" "${ADMISSION_CONTROL}"; \
 | 
			
		||||
                            create-kube-apiserver-opts "${SERVICE_CLUSTER_IP_RANGE}" "${ADMISSION_CONTROL}" "${SERVICE_NODE_PORT_RANGE}"; \
 | 
			
		||||
                            create-kube-controller-manager-opts "${MINION_IPS}"; \
 | 
			
		||||
                            create-kube-scheduler-opts; \
 | 
			
		||||
                            create-kubelet-opts "${MASTER_IP}" "${MASTER_IP}" "${DNS_SERVER_IP}" "${DNS_DOMAIN}";
 | 
			
		||||
 
 | 
			
		||||
@@ -17,13 +17,16 @@ limitations under the License.
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1"
 | 
			
		||||
	pkg_runtime "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	flag "github.com/spf13/pflag"
 | 
			
		||||
@@ -50,7 +53,9 @@ func main() {
 | 
			
		||||
		funcOut = file
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	generator := pkg_runtime.NewConversionGenerator(api.Scheme.Raw())
 | 
			
		||||
	generator := pkg_runtime.NewConversionGenerator(api.Scheme.Raw(), path.Join("github.com/GoogleCloudPlatform/kubernetes/pkg/api", *version))
 | 
			
		||||
	apiShort := generator.AddImport("github.com/GoogleCloudPlatform/kubernetes/pkg/api")
 | 
			
		||||
	generator.AddImport("github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource")
 | 
			
		||||
	// TODO(wojtek-t): Change the overwrites to a flag.
 | 
			
		||||
	generator.OverwritePackage(*version, "")
 | 
			
		||||
	for _, knownType := range api.Scheme.KnownTypes(*version) {
 | 
			
		||||
@@ -58,10 +63,14 @@ func main() {
 | 
			
		||||
			glog.Errorf("error while generating conversion functions for %v: %v", knownType, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	generator.RepackImports(util.NewStringSet())
 | 
			
		||||
	if err := generator.WriteImports(funcOut); err != nil {
 | 
			
		||||
		glog.Fatalf("error while writing imports: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := generator.WriteConversionFunctions(funcOut); err != nil {
 | 
			
		||||
		glog.Fatalf("Error while writing conversion functions: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := generator.RegisterConversionFunctions(funcOut); err != nil {
 | 
			
		||||
	if err := generator.RegisterConversionFunctions(funcOut, fmt.Sprintf("%s.Scheme", apiShort)); err != nil {
 | 
			
		||||
		glog.Fatalf("Error while writing conversion functions: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,14 @@ package main
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	_ "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1"
 | 
			
		||||
	pkg_runtime "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	flag "github.com/spf13/pflag"
 | 
			
		||||
@@ -53,10 +55,14 @@ func main() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	knownVersion := *version
 | 
			
		||||
	registerTo := "api.Scheme"
 | 
			
		||||
	if knownVersion == "api" {
 | 
			
		||||
		knownVersion = api.Scheme.Raw().InternalVersion
 | 
			
		||||
		registerTo = "Scheme"
 | 
			
		||||
	}
 | 
			
		||||
	generator := pkg_runtime.NewDeepCopyGenerator(api.Scheme.Raw())
 | 
			
		||||
	pkgPath := path.Join("github.com/GoogleCloudPlatform/kubernetes/pkg/api", knownVersion)
 | 
			
		||||
	generator := pkg_runtime.NewDeepCopyGenerator(api.Scheme.Raw(), pkgPath, util.NewStringSet("github.com/GoogleCloudPlatform/kubernetes"))
 | 
			
		||||
	generator.AddImport("github.com/GoogleCloudPlatform/kubernetes/pkg/api")
 | 
			
		||||
 | 
			
		||||
	for _, overwrite := range strings.Split(*overwrites, ",") {
 | 
			
		||||
		vals := strings.Split(overwrite, "=")
 | 
			
		||||
@@ -67,13 +73,14 @@ func main() {
 | 
			
		||||
			glog.Errorf("error while generating deep copy functions for %v: %v", knownType, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := generator.WriteImports(funcOut, *version); err != nil {
 | 
			
		||||
	generator.RepackImports()
 | 
			
		||||
	if err := generator.WriteImports(funcOut); err != nil {
 | 
			
		||||
		glog.Fatalf("error while writing imports: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := generator.WriteDeepCopyFunctions(funcOut); err != nil {
 | 
			
		||||
		glog.Fatalf("error while writing deep copy functions: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := generator.RegisterDeepCopyFunctions(funcOut, *version); err != nil {
 | 
			
		||||
	if err := generator.RegisterDeepCopyFunctions(funcOut, registerTo); err != nil {
 | 
			
		||||
		glog.Fatalf("error while registering deep copy functions: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -101,6 +101,7 @@ type APIServer struct {
 | 
			
		||||
	LongRunningRequestRE       string
 | 
			
		||||
	SSHUser                    string
 | 
			
		||||
	SSHKeyfile                 string
 | 
			
		||||
	MaxConnectionBytesPerSec   int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAPIServer creates a new APIServer object with default parameters
 | 
			
		||||
@@ -205,6 +206,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
 | 
			
		||||
	fs.StringVar(&s.LongRunningRequestRE, "long-running-request-regexp", defaultLongRunningRequestRE, "A regular expression matching long running requests which should be excluded from maximum inflight request handling.")
 | 
			
		||||
	fs.StringVar(&s.SSHUser, "ssh-user", "", "If non-empty, use secure SSH proxy to the nodes, using this user name")
 | 
			
		||||
	fs.StringVar(&s.SSHKeyfile, "ssh-keyfile", "", "If non-empty, use secure SSH proxy to the nodes, using this user keyfile")
 | 
			
		||||
	fs.Int64Var(&s.MaxConnectionBytesPerSec, "max-connection-bytes-per-sec", 0, "If non-zero, throttle each user connection to this number of bytes/sec.  Currently only applies to long-running requests")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Longer term we should read this from some config store, rather than a flag.
 | 
			
		||||
@@ -255,7 +257,8 @@ func (s *APIServer) Run(_ []string) error {
 | 
			
		||||
	capabilities.Initialize(capabilities.Capabilities{
 | 
			
		||||
		AllowPrivileged: s.AllowPrivileged,
 | 
			
		||||
		// TODO(vmarmol): Implement support for HostNetworkSources.
 | 
			
		||||
		HostNetworkSources: []string{},
 | 
			
		||||
		HostNetworkSources:                     []string{},
 | 
			
		||||
		PerConnectionBandwidthLimitBytesPerSec: s.MaxConnectionBytesPerSec,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,7 @@ type KubeletServer struct {
 | 
			
		||||
	FileCheckFrequency             time.Duration
 | 
			
		||||
	HTTPCheckFrequency             time.Duration
 | 
			
		||||
	ManifestURL                    string
 | 
			
		||||
	ManifestURLHeader              string
 | 
			
		||||
	EnableServer                   bool
 | 
			
		||||
	Address                        util.IP
 | 
			
		||||
	Port                           uint
 | 
			
		||||
@@ -193,6 +194,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
 | 
			
		||||
	fs.DurationVar(&s.FileCheckFrequency, "file-check-frequency", s.FileCheckFrequency, "Duration between checking config files for new data")
 | 
			
		||||
	fs.DurationVar(&s.HTTPCheckFrequency, "http-check-frequency", s.HTTPCheckFrequency, "Duration between checking http for new data")
 | 
			
		||||
	fs.StringVar(&s.ManifestURL, "manifest-url", s.ManifestURL, "URL for accessing the container manifest")
 | 
			
		||||
	fs.StringVar(&s.ManifestURLHeader, "manifest-url-header", s.ManifestURLHeader, "HTTP header to use when accessing the manifest URL, with the key separated from the value with a ':', as in 'key:value'")
 | 
			
		||||
	fs.BoolVar(&s.EnableServer, "enable-server", s.EnableServer, "Enable the Kubelet's server")
 | 
			
		||||
	fs.Var(&s.Address, "address", "The IP address for the Kubelet to serve on (set to 0.0.0.0 for all interfaces)")
 | 
			
		||||
	fs.UintVar(&s.Port, "port", s.Port, "The port for the Kubelet to serve on. Note that \"kubectl logs\" will not work if you set this flag.") // see #9325
 | 
			
		||||
@@ -295,6 +297,15 @@ func (s *KubeletServer) Run(_ []string) error {
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(2).Infof("Successfully initialized cloud provider: %q from the config file: %q\n", s.CloudProvider, s.CloudConfigFile)
 | 
			
		||||
 | 
			
		||||
	manifestURLHeader := make(http.Header)
 | 
			
		||||
	if s.ManifestURLHeader != "" {
 | 
			
		||||
		pieces := strings.Split(s.ManifestURLHeader, ":")
 | 
			
		||||
		if len(pieces) != 2 {
 | 
			
		||||
			return fmt.Errorf("manifest-url-header must have a single ':' key-value separator, got %q", s.ManifestURLHeader)
 | 
			
		||||
		}
 | 
			
		||||
		manifestURLHeader.Set(pieces[0], pieces[1])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostNetworkSources, err := kubelet.GetValidatedSources(strings.Split(s.HostNetworkSources, ","))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -330,6 +341,7 @@ func (s *KubeletServer) Run(_ []string) error {
 | 
			
		||||
		RootDirectory:                  s.RootDirectory,
 | 
			
		||||
		ConfigFile:                     s.Config,
 | 
			
		||||
		ManifestURL:                    s.ManifestURL,
 | 
			
		||||
		ManifestURLHeader:              manifestURLHeader,
 | 
			
		||||
		FileCheckFrequency:             s.FileCheckFrequency,
 | 
			
		||||
		HTTPCheckFrequency:             s.HTTPCheckFrequency,
 | 
			
		||||
		PodInfraContainerImage:         s.PodInfraContainerImage,
 | 
			
		||||
@@ -604,7 +616,7 @@ func RunKubelet(kcfg *KubeletConfig, builder KubeletBuilder) error {
 | 
			
		||||
	} else {
 | 
			
		||||
		glog.Warning("No api server defined - no events will be sent to API server.")
 | 
			
		||||
	}
 | 
			
		||||
	capabilities.Setup(kcfg.AllowPrivileged, kcfg.HostNetworkSources)
 | 
			
		||||
	capabilities.Setup(kcfg.AllowPrivileged, kcfg.HostNetworkSources, 0)
 | 
			
		||||
 | 
			
		||||
	credentialprovider.SetPreferredDockercfgPath(kcfg.RootDirectory)
 | 
			
		||||
 | 
			
		||||
@@ -660,8 +672,8 @@ func makePodSourceConfig(kc *KubeletConfig) *config.PodConfig {
 | 
			
		||||
 | 
			
		||||
	// define url config source
 | 
			
		||||
	if kc.ManifestURL != "" {
 | 
			
		||||
		glog.Infof("Adding manifest url: %v", kc.ManifestURL)
 | 
			
		||||
		config.NewSourceURL(kc.ManifestURL, kc.NodeName, kc.HTTPCheckFrequency, cfg.Channel(kubelet.HTTPSource))
 | 
			
		||||
		glog.Infof("Adding manifest url %q with HTTP header %v", kc.ManifestURL, kc.ManifestURLHeader)
 | 
			
		||||
		config.NewSourceURL(kc.ManifestURL, kc.ManifestURLHeader, kc.NodeName, kc.HTTPCheckFrequency, cfg.Channel(kubelet.HTTPSource))
 | 
			
		||||
	}
 | 
			
		||||
	if kc.KubeClient != nil {
 | 
			
		||||
		glog.Infof("Watching apiserver")
 | 
			
		||||
@@ -683,6 +695,7 @@ type KubeletConfig struct {
 | 
			
		||||
	RootDirectory                  string
 | 
			
		||||
	ConfigFile                     string
 | 
			
		||||
	ManifestURL                    string
 | 
			
		||||
	ManifestURLHeader              http.Header
 | 
			
		||||
	FileCheckFrequency             time.Duration
 | 
			
		||||
	HTTPCheckFrequency             time.Duration
 | 
			
		||||
	Hostname                       string
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								contrib/for-tests/mount-tester-user/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								contrib/for-tests/mount-tester-user/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
# Copyright 2015 Google Inc. All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
FROM gcr.io/google-containers/mounttest:0.3
 | 
			
		||||
USER 1001
 | 
			
		||||
							
								
								
									
										9
									
								
								contrib/for-tests/mount-tester-user/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								contrib/for-tests/mount-tester-user/Makefile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
all: push
 | 
			
		||||
 | 
			
		||||
TAG = 0.1
 | 
			
		||||
 | 
			
		||||
image:
 | 
			
		||||
	sudo docker build -t gcr.io/google_containers/mounttest-user:$(TAG) .
 | 
			
		||||
 | 
			
		||||
push: image
 | 
			
		||||
	gcloud docker push gcr.io/google_containers/mounttest-user:$(TAG)
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
all: push
 | 
			
		||||
 | 
			
		||||
TAG = 0.2
 | 
			
		||||
TAG = 0.3
 | 
			
		||||
 | 
			
		||||
mt: mt.go
 | 
			
		||||
	CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' ./mt.go
 | 
			
		||||
 
 | 
			
		||||
@@ -25,17 +25,23 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	fsTypePath           = ""
 | 
			
		||||
	fileModePath         = ""
 | 
			
		||||
	readFileContentPath  = ""
 | 
			
		||||
	readWriteNewFilePath = ""
 | 
			
		||||
	fsTypePath          = ""
 | 
			
		||||
	fileModePath        = ""
 | 
			
		||||
	filePermPath        = ""
 | 
			
		||||
	readFileContentPath = ""
 | 
			
		||||
	newFilePath0644     = ""
 | 
			
		||||
	newFilePath0666     = ""
 | 
			
		||||
	newFilePath0777     = ""
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flag.StringVar(&fsTypePath, "fs_type", "", "Path to print the fs type for")
 | 
			
		||||
	flag.StringVar(&fileModePath, "file_mode", "", "Path to print the filemode of")
 | 
			
		||||
	flag.StringVar(&fileModePath, "file_mode", "", "Path to print the mode bits of")
 | 
			
		||||
	flag.StringVar(&filePermPath, "file_perm", "", "Path to print the perms of")
 | 
			
		||||
	flag.StringVar(&readFileContentPath, "file_content", "", "Path to read the file content from")
 | 
			
		||||
	flag.StringVar(&readWriteNewFilePath, "rw_new_file", "", "Path to write to and read from")
 | 
			
		||||
	flag.StringVar(&newFilePath0644, "new_file_0644", "", "Path to write to and read from with perm 0644")
 | 
			
		||||
	flag.StringVar(&newFilePath0666, "new_file_0666", "", "Path to write to and read from with perm 0666")
 | 
			
		||||
	flag.StringVar(&newFilePath0777, "new_file_0777", "", "Path to write to and read from with perm 0777")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This program performs some tests on the filesystem as dictated by the
 | 
			
		||||
@@ -48,6 +54,9 @@ func main() {
 | 
			
		||||
		errs = []error{}
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	// Clear the umask so we can set any mode bits we want.
 | 
			
		||||
	syscall.Umask(0000)
 | 
			
		||||
 | 
			
		||||
	// NOTE: the ordering of execution of the various command line
 | 
			
		||||
	// flags is intentional and allows a single command to:
 | 
			
		||||
	//
 | 
			
		||||
@@ -62,7 +71,17 @@ func main() {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = readWriteNewFile(readWriteNewFilePath)
 | 
			
		||||
	err = readWriteNewFile(newFilePath0644, 0644)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = readWriteNewFile(newFilePath0666, 0666)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = readWriteNewFile(newFilePath0777, 0777)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -72,6 +91,11 @@ func main() {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = filePerm(filePermPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = readFileContent(readFileContentPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
@@ -94,7 +118,7 @@ func fsType(path string) error {
 | 
			
		||||
 | 
			
		||||
	buf := syscall.Statfs_t{}
 | 
			
		||||
	if err := syscall.Statfs(path, &buf); err != nil {
 | 
			
		||||
		fmt.Printf("error from statfs(%q): %v", path, err)
 | 
			
		||||
		fmt.Printf("error from statfs(%q): %v\n", path, err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -122,6 +146,21 @@ func fileMode(path string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filePerm(path string) error {
 | 
			
		||||
	if path == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fileinfo, err := os.Lstat(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("error from Lstat(%q): %v\n", path, err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Printf("perms of file %q: %v\n", path, fileinfo.Mode().Perm())
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func readFileContent(path string) error {
 | 
			
		||||
	if path == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -138,13 +177,13 @@ func readFileContent(path string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func readWriteNewFile(path string) error {
 | 
			
		||||
func readWriteNewFile(path string, perm os.FileMode) error {
 | 
			
		||||
	if path == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	content := "mount-tester new file\n"
 | 
			
		||||
	err := ioutil.WriteFile(path, []byte(content), 0644)
 | 
			
		||||
	err := ioutil.WriteFile(path, []byte(content), perm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Printf("error writing new file %q: %v\n", path, err)
 | 
			
		||||
		return err
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# The address on the local server to listen to.
 | 
			
		||||
KUBE_API_ADDRESS="--address=127.0.0.1"
 | 
			
		||||
KUBE_API_ADDRESS="--insecure-bind-address=127.0.0.1"
 | 
			
		||||
 | 
			
		||||
# The port on the local server to listen on.
 | 
			
		||||
# KUBE_API_PORT="--port=8080"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
[Unit]
 | 
			
		||||
Description=Kubernetes API Server
 | 
			
		||||
Documentation=https://github.com/GoogleCloudPlatform/kubernetes
 | 
			
		||||
After=network.target
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
EnvironmentFile=-/etc/kubernetes/config
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										283
									
								
								contrib/submit-queue/github/github.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								contrib/submit-queue/github/github.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 github
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	"github.com/google/go-github/github"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func MakeClient(token string) *github.Client {
 | 
			
		||||
	if len(token) > 0 {
 | 
			
		||||
		ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
 | 
			
		||||
		tc := oauth2.NewClient(oauth2.NoContext, ts)
 | 
			
		||||
		return github.NewClient(tc)
 | 
			
		||||
	}
 | 
			
		||||
	return github.NewClient(nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hasLabel(labels []github.Label, name string) bool {
 | 
			
		||||
	for i := range labels {
 | 
			
		||||
		label := &labels[i]
 | 
			
		||||
		if label.Name != nil && *label.Name == name {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func hasLabels(labels []github.Label, names []string) bool {
 | 
			
		||||
	for i := range names {
 | 
			
		||||
		if !hasLabel(labels, names[i]) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fetchAllPRs(client *github.Client, user, project string) ([]github.PullRequest, error) {
 | 
			
		||||
	page := 1
 | 
			
		||||
	var result []github.PullRequest
 | 
			
		||||
	for {
 | 
			
		||||
		glog.V(4).Infof("Fetching page %d", page)
 | 
			
		||||
		listOpts := &github.PullRequestListOptions{
 | 
			
		||||
			Sort:        "desc",
 | 
			
		||||
			ListOptions: github.ListOptions{PerPage: 100, Page: page},
 | 
			
		||||
		}
 | 
			
		||||
		prs, response, err := client.PullRequests.List(user, project, listOpts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		result = append(result, prs...)
 | 
			
		||||
		if response.LastPage == 0 || response.LastPage == page {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		page++
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PRFunction func(*github.Client, *github.PullRequest, *github.Issue) error
 | 
			
		||||
 | 
			
		||||
type FilterConfig struct {
 | 
			
		||||
	MinPRNumber            int
 | 
			
		||||
	UserWhitelist          []string
 | 
			
		||||
	WhitelistOverride      string
 | 
			
		||||
	RequiredStatusContexts []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// For each PR in the project that matches:
 | 
			
		||||
//   * pr.Number > minPRNumber
 | 
			
		||||
//   * is mergeable
 | 
			
		||||
//   * has labels "cla: yes", "lgtm"
 | 
			
		||||
//   * combinedStatus = 'success' (e.g. all hooks have finished success in github)
 | 
			
		||||
// Run the specified function
 | 
			
		||||
func ForEachCandidatePRDo(client *github.Client, user, project string, fn PRFunction, once bool, config *FilterConfig) error {
 | 
			
		||||
	// Get all PRs
 | 
			
		||||
	prs, err := fetchAllPRs(client, user, project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userSet := util.StringSet{}
 | 
			
		||||
	userSet.Insert(config.UserWhitelist...)
 | 
			
		||||
 | 
			
		||||
	for ix := range prs {
 | 
			
		||||
		if prs[ix].User == nil || prs[ix].User.Login == nil {
 | 
			
		||||
			glog.V(2).Infof("Skipping PR %d with no user info %v.", *prs[ix].Number, *prs[ix].User)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if *prs[ix].Number < config.MinPRNumber {
 | 
			
		||||
			glog.V(6).Infof("Dropping %d < %d", *prs[ix].Number, config.MinPRNumber)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pr, _, err := client.PullRequests.Get(user, project, *prs[ix].Number)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("Error getting pull request: %v", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		glog.V(2).Infof("----==== %d ====----", *pr.Number)
 | 
			
		||||
 | 
			
		||||
		// Labels are actually stored in the Issues API, not the Pull Request API
 | 
			
		||||
		issue, _, err := client.Issues.Get(user, project, *pr.Number)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("Failed to get issue for PR: %v", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		glog.V(8).Infof("%v", issue.Labels)
 | 
			
		||||
		if !hasLabels(issue.Labels, []string{"lgtm", "cla: yes"}) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !hasLabel(issue.Labels, config.WhitelistOverride) && !userSet.Has(*prs[ix].User.Login) {
 | 
			
		||||
			glog.V(4).Infof("Dropping %d since %s isn't in whitelist and %s isn't present", *prs[ix].Number, *prs[ix].User.Login, config.WhitelistOverride)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// This is annoying, github appears to only temporarily cache mergeability, if it is nil, wait
 | 
			
		||||
		// for an async refresh and retry.
 | 
			
		||||
		if pr.Mergeable == nil {
 | 
			
		||||
			glog.Infof("Waiting for mergeability on %s %d", *pr.Title, *pr.Number)
 | 
			
		||||
			// TODO: determine what a good empirical setting for this is.
 | 
			
		||||
			time.Sleep(10 * time.Second)
 | 
			
		||||
			pr, _, err = client.PullRequests.Get(user, project, *prs[ix].Number)
 | 
			
		||||
		}
 | 
			
		||||
		if pr.Mergeable == nil {
 | 
			
		||||
			glog.Errorf("No mergeability information for %s %d, Skipping.", *pr.Title, *pr.Number)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !*pr.Mergeable {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Validate the status information for this PR
 | 
			
		||||
		ok, err := ValidateStatus(client, user, project, *pr.Number, config.RequiredStatusContexts, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("Error validating PR status: %v", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err := fn(client, pr, issue); err != nil {
 | 
			
		||||
			glog.Errorf("Failed to run user function: %v", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if once {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCommitStatus(client *github.Client, user, project string, prNumber int) ([]*github.CombinedStatus, error) {
 | 
			
		||||
	commits, _, err := client.PullRequests.ListCommits(user, project, prNumber, &github.ListOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	commitStatus := make([]*github.CombinedStatus, len(commits))
 | 
			
		||||
	for ix := range commits {
 | 
			
		||||
		commit := &commits[ix]
 | 
			
		||||
		statusList, _, err := client.Repositories.GetCombinedStatus(user, project, *commit.SHA, &github.ListOptions{})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		commitStatus[ix] = statusList
 | 
			
		||||
	}
 | 
			
		||||
	return commitStatus, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Gets the current status of a PR by introspecting the status of the commits in the PR.
 | 
			
		||||
// The rules are:
 | 
			
		||||
//    * If any member of the 'requiredContexts' list is missing, it is 'incomplete'
 | 
			
		||||
//    * If any commit is 'pending', the PR is 'pending'
 | 
			
		||||
//    * If any commit is 'error', the PR is in 'error'
 | 
			
		||||
//    * If any commit is 'failure', the PR is 'failure'
 | 
			
		||||
//    * Otherwise the PR is 'success'
 | 
			
		||||
func GetStatus(client *github.Client, user, project string, prNumber int, requiredContexts []string) (string, error) {
 | 
			
		||||
	statusList, err := getCommitStatus(client, user, project, prNumber)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return computeStatus(statusList, requiredContexts), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func computeStatus(statusList []*github.CombinedStatus, requiredContexts []string) string {
 | 
			
		||||
	states := util.StringSet{}
 | 
			
		||||
	providers := util.StringSet{}
 | 
			
		||||
	for ix := range statusList {
 | 
			
		||||
		status := statusList[ix]
 | 
			
		||||
		glog.V(8).Infof("Checking commit: %s", *status.SHA)
 | 
			
		||||
		glog.V(8).Infof("Checking commit: %v", status)
 | 
			
		||||
		states.Insert(*status.State)
 | 
			
		||||
 | 
			
		||||
		for _, subStatus := range status.Statuses {
 | 
			
		||||
			glog.V(8).Infof("Found status from: %v", subStatus)
 | 
			
		||||
			providers.Insert(*subStatus.Context)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, provider := range requiredContexts {
 | 
			
		||||
		if !providers.Has(provider) {
 | 
			
		||||
			glog.V(8).Infof("Failed to find %s in %v", provider, providers)
 | 
			
		||||
			return "incomplete"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case states.Has("pending"):
 | 
			
		||||
		return "pending"
 | 
			
		||||
	case states.Has("error"):
 | 
			
		||||
		return "error"
 | 
			
		||||
	case states.Has("failure"):
 | 
			
		||||
		return "failure"
 | 
			
		||||
	default:
 | 
			
		||||
		return "success"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Make sure that the combined status for all commits in a PR is 'success'
 | 
			
		||||
// if 'waitForPending' is true, this function will wait until the PR is no longer pending (all checks have run)
 | 
			
		||||
func ValidateStatus(client *github.Client, user, project string, prNumber int, requiredContexts []string, waitOnPending bool) (bool, error) {
 | 
			
		||||
	pending := true
 | 
			
		||||
	for pending {
 | 
			
		||||
		status, err := GetStatus(client, user, project, prNumber, requiredContexts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		switch status {
 | 
			
		||||
		case "error", "failure":
 | 
			
		||||
			return false, nil
 | 
			
		||||
		case "pending":
 | 
			
		||||
			if !waitOnPending {
 | 
			
		||||
				return false, nil
 | 
			
		||||
			}
 | 
			
		||||
			pending = true
 | 
			
		||||
			glog.V(4).Info("PR is pending, waiting for 30 seconds")
 | 
			
		||||
			time.Sleep(30 * time.Second)
 | 
			
		||||
		case "success":
 | 
			
		||||
			return true, nil
 | 
			
		||||
		case "incomplete":
 | 
			
		||||
			return false, nil
 | 
			
		||||
		default:
 | 
			
		||||
			return false, fmt.Errorf("unknown status: %s", status)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Wait for a PR to move into Pending.  This is useful because the request to test a PR again
 | 
			
		||||
// is asynchronous with the PR actually moving into a pending state
 | 
			
		||||
// TODO: add a timeout
 | 
			
		||||
func WaitForPending(client *github.Client, user, project string, prNumber int) error {
 | 
			
		||||
	for {
 | 
			
		||||
		status, err := GetStatus(client, user, project, prNumber, []string{})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if status == "pending" {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		glog.V(4).Info("PR is not pending, waiting for 30 seconds")
 | 
			
		||||
		time.Sleep(30 * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										390
									
								
								contrib/submit-queue/github/github_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								contrib/submit-queue/github/github_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,390 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 github
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-github/github"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func stringPtr(val string) *string { return &val }
 | 
			
		||||
 | 
			
		||||
func TestHasLabel(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		labels   []github.Label
 | 
			
		||||
		label    string
 | 
			
		||||
		hasLabel bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("foo")},
 | 
			
		||||
			},
 | 
			
		||||
			label:    "foo",
 | 
			
		||||
			hasLabel: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("bar")},
 | 
			
		||||
			},
 | 
			
		||||
			label:    "foo",
 | 
			
		||||
			hasLabel: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("bar")},
 | 
			
		||||
				{Name: stringPtr("foo")},
 | 
			
		||||
			},
 | 
			
		||||
			label:    "foo",
 | 
			
		||||
			hasLabel: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("bar")},
 | 
			
		||||
				{Name: stringPtr("baz")},
 | 
			
		||||
			},
 | 
			
		||||
			label:    "foo",
 | 
			
		||||
			hasLabel: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if test.hasLabel != hasLabel(test.labels, test.label) {
 | 
			
		||||
			t.Errorf("Unexpected output: %v", test)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestHasLabels(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		labels     []github.Label
 | 
			
		||||
		seekLabels []string
 | 
			
		||||
		hasLabel   bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("foo")},
 | 
			
		||||
			},
 | 
			
		||||
			seekLabels: []string{"foo"},
 | 
			
		||||
			hasLabel:   true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("bar")},
 | 
			
		||||
			},
 | 
			
		||||
			seekLabels: []string{"foo"},
 | 
			
		||||
			hasLabel:   false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("bar")},
 | 
			
		||||
				{Name: stringPtr("foo")},
 | 
			
		||||
			},
 | 
			
		||||
			seekLabels: []string{"foo"},
 | 
			
		||||
			hasLabel:   true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("bar")},
 | 
			
		||||
				{Name: stringPtr("baz")},
 | 
			
		||||
			},
 | 
			
		||||
			seekLabels: []string{"foo"},
 | 
			
		||||
			hasLabel:   false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			labels: []github.Label{
 | 
			
		||||
				{Name: stringPtr("foo")},
 | 
			
		||||
			},
 | 
			
		||||
			seekLabels: []string{"foo", "bar"},
 | 
			
		||||
			hasLabel:   false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		if test.hasLabel != hasLabels(test.labels, test.seekLabels) {
 | 
			
		||||
			t.Errorf("Unexpected output: %v", test)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initTest() (*github.Client, *httptest.Server, *http.ServeMux) {
 | 
			
		||||
	// test server
 | 
			
		||||
	mux := http.NewServeMux()
 | 
			
		||||
	server := httptest.NewServer(mux)
 | 
			
		||||
 | 
			
		||||
	// github client configured to use test server
 | 
			
		||||
	client := github.NewClient(nil)
 | 
			
		||||
	url, _ := url.Parse(server.URL)
 | 
			
		||||
	client.BaseURL = url
 | 
			
		||||
	client.UploadURL = url
 | 
			
		||||
 | 
			
		||||
	return client, server, mux
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFetchAllPRs(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		PullRequests [][]github.PullRequest
 | 
			
		||||
		Pages        []int
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			PullRequests: [][]github.PullRequest{
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Pages: []int{0},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			PullRequests: [][]github.PullRequest{
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Pages: []int{4, 4, 4, 0},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			PullRequests: [][]github.PullRequest{
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					{},
 | 
			
		||||
					{},
 | 
			
		||||
					{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Pages: []int{3, 3, 3, 0},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		client, server, mux := initTest()
 | 
			
		||||
		count := 0
 | 
			
		||||
		prCount := 0
 | 
			
		||||
		mux.HandleFunc("/repos/foo/bar/pulls", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
			if r.Method != "GET" {
 | 
			
		||||
				t.Errorf("Unexpected method: %s", r.Method)
 | 
			
		||||
			}
 | 
			
		||||
			if r.URL.Query().Get("page") != strconv.Itoa(count+1) {
 | 
			
		||||
				t.Errorf("Unexpected page: %s", r.URL.Query().Get("page"))
 | 
			
		||||
			}
 | 
			
		||||
			if r.URL.Query().Get("sort") != "desc" {
 | 
			
		||||
				t.Errorf("Unexpected sort: %s", r.URL.Query().Get("sort"))
 | 
			
		||||
			}
 | 
			
		||||
			if r.URL.Query().Get("per_page") != "100" {
 | 
			
		||||
				t.Errorf("Unexpected per_page: %s", r.URL.Query().Get("per_page"))
 | 
			
		||||
			}
 | 
			
		||||
			w.Header().Add("Link",
 | 
			
		||||
				fmt.Sprintf("<https://api.github.com/?page=%d>; rel=\"last\"", test.Pages[count]))
 | 
			
		||||
			w.WriteHeader(http.StatusOK)
 | 
			
		||||
			data, err := json.Marshal(test.PullRequests[count])
 | 
			
		||||
			prCount += len(test.PullRequests[count])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Errorf("Unexpected error: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			w.Write(data)
 | 
			
		||||
			count++
 | 
			
		||||
		})
 | 
			
		||||
		prs, err := fetchAllPRs(client, "foo", "bar")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("unexpected error: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		if len(prs) != prCount {
 | 
			
		||||
			t.Errorf("unexpected output %d vs %d", len(prs), prCount)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if count != len(test.PullRequests) {
 | 
			
		||||
			t.Errorf("unexpected number of fetches: %d", count)
 | 
			
		||||
		}
 | 
			
		||||
		server.Close()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestComputeStatus(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		statusList       []*github.CombinedStatus
 | 
			
		||||
		requiredContexts []string
 | 
			
		||||
		expected         string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			expected: "success",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("error"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("pending"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			expected: "pending",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("pending"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			expected: "pending",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("failure"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			expected: "failure",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("failure"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("error"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			expected: "error",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "incomplete",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("pending"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "incomplete",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("failure"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "incomplete",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{State: stringPtr("failure"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("error"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "incomplete",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{
 | 
			
		||||
					State: stringPtr("success"),
 | 
			
		||||
					SHA:   stringPtr("abcdef"),
 | 
			
		||||
					Statuses: []github.RepoStatus{
 | 
			
		||||
						{Context: stringPtr("context")},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "success",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{
 | 
			
		||||
					State: stringPtr("pending"),
 | 
			
		||||
					SHA:   stringPtr("abcdef"),
 | 
			
		||||
					Statuses: []github.RepoStatus{
 | 
			
		||||
						{Context: stringPtr("context")},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "pending",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{
 | 
			
		||||
					State: stringPtr("error"),
 | 
			
		||||
					SHA:   stringPtr("abcdef"),
 | 
			
		||||
					Statuses: []github.RepoStatus{
 | 
			
		||||
						{Context: stringPtr("context")},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "error",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			statusList: []*github.CombinedStatus{
 | 
			
		||||
				{
 | 
			
		||||
					State: stringPtr("failure"),
 | 
			
		||||
					SHA:   stringPtr("abcdef"),
 | 
			
		||||
					Statuses: []github.RepoStatus{
 | 
			
		||||
						{Context: stringPtr("context")},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
				{State: stringPtr("success"), SHA: stringPtr("abcdef")},
 | 
			
		||||
			},
 | 
			
		||||
			requiredContexts: []string{"context"},
 | 
			
		||||
			expected:         "failure",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		// ease of use, reduce boilerplate in test cases
 | 
			
		||||
		if test.requiredContexts == nil {
 | 
			
		||||
			test.requiredContexts = []string{}
 | 
			
		||||
		}
 | 
			
		||||
		status := computeStatus(test.statusList, test.requiredContexts)
 | 
			
		||||
		if test.expected != status {
 | 
			
		||||
			t.Errorf("expected: %s, saw %s", test.expected, status)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										91
									
								
								contrib/submit-queue/jenkins/jenkins.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								contrib/submit-queue/jenkins/jenkins.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
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 jenkins
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type JenkinsClient struct {
 | 
			
		||||
	Host string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Queue struct {
 | 
			
		||||
	Builds             []Build `json:"builds"`
 | 
			
		||||
	LastCompletedBuild Build   `json:"lastCompletedBuild"`
 | 
			
		||||
	LastStableBuild    Build   `json:"lastStableBuild"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Build struct {
 | 
			
		||||
	Number int    `json:"number"`
 | 
			
		||||
	URL    string `json:"url"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Job struct {
 | 
			
		||||
	Result    string `json:"result"`
 | 
			
		||||
	ID        string `json:"id"`
 | 
			
		||||
	Timestamp int    `json:timestamp`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (j *JenkinsClient) request(path string) ([]byte, error) {
 | 
			
		||||
	url := j.Host + path
 | 
			
		||||
	glog.V(3).Infof("Hitting: %s", url)
 | 
			
		||||
	res, err := http.Get(url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer res.Body.Close()
 | 
			
		||||
	return ioutil.ReadAll(res.Body)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (j *JenkinsClient) GetJob(name string) (*Queue, error) {
 | 
			
		||||
	data, err := j.request("/job/" + name + "/api/json")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(8).Infof("Got data: %s", string(data))
 | 
			
		||||
	q := &Queue{}
 | 
			
		||||
	if err := json.Unmarshal(data, q); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return q, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (j *JenkinsClient) GetLastCompletedBuild(name string) (*Job, error) {
 | 
			
		||||
	data, err := j.request("/job/" + name + "/lastCompletedBuild/api/json")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(8).Infof("Got data: %s", string(data))
 | 
			
		||||
	job := &Job{}
 | 
			
		||||
	if err := json.Unmarshal(data, job); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return job, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (j *JenkinsClient) IsBuildStable(name string) (bool, error) {
 | 
			
		||||
	q, err := j.GetLastCompletedBuild(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return q.Result == "SUCCESS", nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										162
									
								
								contrib/submit-queue/submit-queue.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								contrib/submit-queue/submit-queue.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
// A simple binary for merging PR that match a criteria
 | 
			
		||||
// Usage:
 | 
			
		||||
//   submit-queue -token=<github-access-token> -user-whitelist=<file> --jenkins-host=http://some.host [-min-pr-number=<number>] [-dry-run] [-once]
 | 
			
		||||
//
 | 
			
		||||
// Details:
 | 
			
		||||
/*
 | 
			
		||||
Usage of ./submit-queue:
 | 
			
		||||
  -alsologtostderr=false: log to standard error as well as files
 | 
			
		||||
  -dry-run=false: If true, don't actually merge anything
 | 
			
		||||
  -jenkins-job="kubernetes-e2e-gce,kubernetes-e2e-gke-ci,kubernetes-build": Comma separated list of jobs in Jenkins to use for stability testing
 | 
			
		||||
  -log_backtrace_at=:0: when logging hits line file:N, emit a stack trace
 | 
			
		||||
  -log_dir="": If non-empty, write log files in this directory
 | 
			
		||||
  -logtostderr=false: log to standard error instead of files
 | 
			
		||||
  -min-pr-number=0: The minimum PR to start with [default: 0]
 | 
			
		||||
  -once=false: If true, only merge one PR, don't run forever
 | 
			
		||||
  -stderrthreshold=0: logs at or above this threshold go to stderr
 | 
			
		||||
  -token="": The OAuth Token to use for requests.
 | 
			
		||||
  -user-whitelist="": Path to a whitelist file that contains users to auto-merge.  Required.
 | 
			
		||||
  -v=0: log level for V logs
 | 
			
		||||
  -vmodule=: comma-separated list of pattern=N settings for file-filtered logging
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/contrib/submit-queue/github"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/contrib/submit-queue/jenkins"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/glog"
 | 
			
		||||
	github_api "github.com/google/go-github/github"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	token             = flag.String("token", "", "The OAuth Token to use for requests.")
 | 
			
		||||
	minPRNumber       = flag.Int("min-pr-number", 0, "The minimum PR to start with [default: 0]")
 | 
			
		||||
	dryrun            = flag.Bool("dry-run", false, "If true, don't actually merge anything")
 | 
			
		||||
	oneOff            = flag.Bool("once", false, "If true, only merge one PR, don't run forever")
 | 
			
		||||
	jobs              = flag.String("jenkins-jobs", "kubernetes-e2e-gce,kubernetes-e2e-gke-ci,kubernetes-build", "Comma separated list of jobs in Jenkins to use for stability testing")
 | 
			
		||||
	jenkinsHost       = flag.String("jenkins-host", "", "The URL for the jenkins job to watch")
 | 
			
		||||
	userWhitelist     = flag.String("user-whitelist", "", "Path to a whitelist file that contains users to auto-merge.  Required.")
 | 
			
		||||
	requiredContexts  = flag.String("required-contexts", "cla/google,Shippable,continuous-integration/travis-ci/pr,Jenkins GCE e2e", "Comma separate list of status contexts required for a PR to be considered ok to merge")
 | 
			
		||||
	whitelistOverride = flag.String("whitelist-override-label", "ok-to-merge", "Github label, if present on a PR it will be merged even if the author isn't in the whitelist")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	org     = "GoogleCloudPlatform"
 | 
			
		||||
	project = "kubernetes"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// This is called on a potentially mergeable PR
 | 
			
		||||
func runE2ETests(client *github_api.Client, pr *github_api.PullRequest, issue *github_api.Issue) error {
 | 
			
		||||
	// Test if the build is stable in Jenkins
 | 
			
		||||
	jenkinsClient := &jenkins.JenkinsClient{Host: *jenkinsHost}
 | 
			
		||||
	builds := strings.Split(*jobs, ",")
 | 
			
		||||
	for _, build := range builds {
 | 
			
		||||
		stable, err := jenkinsClient.IsBuildStable(build)
 | 
			
		||||
		glog.V(2).Infof("Checking build stability for %s", build)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if !stable {
 | 
			
		||||
			glog.Errorf("Build %s isn't stable, skipping!", build)
 | 
			
		||||
			return errors.New("Unstable build")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	glog.V(2).Infof("Build is stable.")
 | 
			
		||||
	// Ask for a fresh build
 | 
			
		||||
	glog.V(4).Infof("Asking PR builder to build %d", *pr.Number)
 | 
			
		||||
	body := "@k8s-bot test this [testing build queue, sorry for the noise]"
 | 
			
		||||
	if _, _, err := client.Issues.CreateComment(org, project, *pr.Number, &github_api.IssueComment{Body: &body}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Wait for the build to start
 | 
			
		||||
	err := github.WaitForPending(client, org, project, *pr.Number)
 | 
			
		||||
 | 
			
		||||
	// Wait for the status to go back to 'success'
 | 
			
		||||
	ok, err := github.ValidateStatus(client, org, project, *pr.Number, []string{}, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		glog.Infof("Status after build is not 'success', skipping PR %d", *pr.Number)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if !*dryrun {
 | 
			
		||||
		glog.Infof("Merging PR: %d", *pr.Number)
 | 
			
		||||
		mergeBody := "Automatic merge from SubmitQueue"
 | 
			
		||||
		if _, _, err := client.Issues.CreateComment(org, project, *pr.Number, &github_api.IssueComment{Body: &mergeBody}); err != nil {
 | 
			
		||||
			glog.Warningf("Failed to create merge comment: %v", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		_, _, err := client.PullRequests.Merge(org, project, *pr.Number, "Auto commit by PR queue bot")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	glog.Infof("Skipping actual merge because --dry-run is set")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadWhitelist(file string) ([]string, error) {
 | 
			
		||||
	fp, err := os.Open(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer fp.Close()
 | 
			
		||||
	scanner := bufio.NewScanner(fp)
 | 
			
		||||
	result := []string{}
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		result = append(result, scanner.Text())
 | 
			
		||||
	}
 | 
			
		||||
	return result, scanner.Err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
	if len(*userWhitelist) == 0 {
 | 
			
		||||
		glog.Fatalf("--user-whitelist is required.")
 | 
			
		||||
	}
 | 
			
		||||
	if len(*jenkinsHost) == 0 {
 | 
			
		||||
		glog.Fatalf("--jenkins-host is required.")
 | 
			
		||||
	}
 | 
			
		||||
	client := github.MakeClient(*token)
 | 
			
		||||
 | 
			
		||||
	users, err := loadWhitelist(*userWhitelist)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		glog.Fatalf("error loading user whitelist: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	requiredContexts := strings.Split(*requiredContexts, ",")
 | 
			
		||||
	config := &github.FilterConfig{
 | 
			
		||||
		MinPRNumber:            *minPRNumber,
 | 
			
		||||
		UserWhitelist:          users,
 | 
			
		||||
		RequiredStatusContexts: requiredContexts,
 | 
			
		||||
		WhitelistOverride:      *whitelistOverride,
 | 
			
		||||
	}
 | 
			
		||||
	for !*oneOff {
 | 
			
		||||
		if err := github.ForEachCandidatePRDo(client, org, project, runE2ETests, *oneOff, config); err != nil {
 | 
			
		||||
			glog.Fatalf("Error getting candidate PRs: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								contrib/submit-queue/whitelist.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								contrib/submit-queue/whitelist.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
brendandburns
 | 
			
		||||
thockin
 | 
			
		||||
mikedanese
 | 
			
		||||
a-robinson
 | 
			
		||||
saad-ali
 | 
			
		||||
lavalamp
 | 
			
		||||
smarterclayton
 | 
			
		||||
justinsb
 | 
			
		||||
satnam6502
 | 
			
		||||
derekwaynecarr
 | 
			
		||||
dchen1107
 | 
			
		||||
zmerlynn
 | 
			
		||||
erictune
 | 
			
		||||
eparis
 | 
			
		||||
caesarxuchao
 | 
			
		||||
wojtek-t
 | 
			
		||||
jlowdermilk
 | 
			
		||||
yifan-gu
 | 
			
		||||
nikhiljindal
 | 
			
		||||
markturansky
 | 
			
		||||
pmorie
 | 
			
		||||
yujuhong
 | 
			
		||||
roberthbailey
 | 
			
		||||
vishh
 | 
			
		||||
deads2k
 | 
			
		||||
bprashanth
 | 
			
		||||
cjcullen
 | 
			
		||||
liggitt
 | 
			
		||||
bgrant0607
 | 
			
		||||
fgrzadkowski
 | 
			
		||||
jayunit100
 | 
			
		||||
mbforbes
 | 
			
		||||
ArtfulCoder
 | 
			
		||||
piosz
 | 
			
		||||
davidopp
 | 
			
		||||
ixdy
 | 
			
		||||
marekbiskup
 | 
			
		||||
gmarek
 | 
			
		||||
ghodss
 | 
			
		||||
krousey
 | 
			
		||||
quinton-hoole
 | 
			
		||||
@@ -134,7 +134,7 @@ Here are all the solutions mentioned above in table form.
 | 
			
		||||
IaaS Provider        | Config. Mgmt | OS     | Networking  | Docs                                              | Conforms | Support Level
 | 
			
		||||
-------------------- | ------------ | ------ | ----------  | ---------------------------------------------     | ---------| ----------------------------
 | 
			
		||||
GKE                  |              |        | GCE         | [docs](https://cloud.google.com/container-engine) |          | Commercial
 | 
			
		||||
Vagrant              | Saltstack    | Fedora | OVS         | [docs](vagrant.md)                                |          | Project
 | 
			
		||||
Vagrant              | Saltstack    | Fedora | OVS         | [docs](vagrant.md)                                | [✓][2]   | Project
 | 
			
		||||
GCE                  | Saltstack    | Debian | GCE         | [docs](gce.md)                                    | [✓][1]   | Project
 | 
			
		||||
Azure                | CoreOS       | CoreOS | Weave       | [docs](coreos/azure/README.md)                    |          | Community ([@errordeveloper](https://github.com/errordeveloper), [@squillace](https://github.com/squillace), [@chanezon](https://github.com/chanezon), [@crossorigin](https://github.com/crossorigin))
 | 
			
		||||
Docker Single Node   | custom       | N/A    | local       | [docs](docker.md)                                 |          | Project (@brendandburns)
 | 
			
		||||
@@ -164,7 +164,7 @@ Local                |              |        | _none_      | [docs](locally.md)
 | 
			
		||||
libvirt/KVM          | CoreOS       | CoreOS | libvirt/KVM | [docs](libvirt-coreos.md)                         |          | Community (@lhuard1A)
 | 
			
		||||
oVirt                |              |        |             | [docs](ovirt.md)                                  |          | Community (@simon3z)
 | 
			
		||||
Rackspace            | CoreOS       | CoreOS | flannel     | [docs](rackspace.md)                              |          | Community (@doublerr)
 | 
			
		||||
any                  | any          | any    | any         | [docs](scratch.md)                              |          | Community (@doublerr)
 | 
			
		||||
any                  | any          | any    | any         | [docs](scratch.md)                              |          | Community (@erictune)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
*Note*: The above table is ordered by version test/used in notes followed by support level.
 | 
			
		||||
@@ -189,6 +189,8 @@ Definition of columns:
 | 
			
		||||
<!-- reference style links below here -->
 | 
			
		||||
<!-- GCE conformance test result -->
 | 
			
		||||
[1]: https://gist.github.com/erictune/4cabc010906afbcc5061
 | 
			
		||||
<!-- Vagrant conformance test result -->
 | 
			
		||||
[2]: https://gist.github.com/derekwaynecarr/505e56036cdf010bf6b6
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ interested in just starting to explore Kubernetes, we recommend that you start t
 | 
			
		||||
 | 
			
		||||
_Note_:
 | 
			
		||||
There is a [bug](https://github.com/docker/docker/issues/14106) in Docker 1.7.0 that prevents this from working correctly.
 | 
			
		||||
Please install Docker 1.6.2 or wait for Docker 1.7.1.
 | 
			
		||||
Please install Docker 1.6.2 or Docker 1.7.1.
 | 
			
		||||
 | 
			
		||||
**Table of Contents**
 | 
			
		||||
 | 
			
		||||
@@ -83,7 +83,7 @@ The first step in the process is to initialize the master node.
 | 
			
		||||
Clone the Kubernetes repo, and run [master.sh](docker-multinode/master.sh) on the master machine with root:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
export K8S_VERSION=<your_k8s_version>
 | 
			
		||||
export K8S_VERSION=<your_k8s_version (e.g. 1.0.1)>
 | 
			
		||||
cd kubernetes/cluster/docker-multinode
 | 
			
		||||
./master.sh
 | 
			
		||||
```
 | 
			
		||||
@@ -99,7 +99,8 @@ Once your master is up and running you can add one or more workers on different
 | 
			
		||||
Clone the Kubernetes repo, and run [worker.sh](docker-multinode/worker.sh) on the worker machine with root:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
export K8S_VERSION=<your_k8s_version> MASTER_IP=<your_master_ip>
 | 
			
		||||
export K8S_VERSION=<your_k8s_version (e.g. 1.0.1)>
 | 
			
		||||
export MASTER_IP=<your_master_ip (e.g. 1.2.3.4)>
 | 
			
		||||
cd kubernetes/cluster/docker-multinode
 | 
			
		||||
./worker.sh
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -78,13 +78,13 @@ output of /proc/cmdline
 | 
			
		||||
### Step One: Run etcd
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
docker run --net=host -d gcr.io/google_containers/etcd:2.0.9 /usr/local/bin/etcd --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 --data-dir=/var/etcd/data
 | 
			
		||||
docker run --net=host -d gcr.io/google_containers/etcd:2.0.12 /usr/local/bin/etcd --addr=127.0.0.1:4001 --bind-addr=0.0.0.0:4001 --data-dir=/var/etcd/data
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Step Two: Run the master
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
docker run --net=host -d -v /var/run/docker.sock:/var/run/docker.sock  gcr.io/google_containers/hyperkube:v0.21.2 /hyperkube kubelet --api_servers=http://localhost:8080 --v=2 --address=0.0.0.0 --enable_server --hostname_override=127.0.0.1 --config=/etc/kubernetes/manifests
 | 
			
		||||
docker run --net=host -d -v /var/run/docker.sock:/var/run/docker.sock  gcr.io/google_containers/hyperkube:v1.0.1 /hyperkube kubelet --api_servers=http://localhost:8080 --v=2 --address=0.0.0.0 --enable_server --hostname_override=127.0.0.1 --config=/etc/kubernetes/manifests
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This actually runs the kubelet, which in turn runs a [pod](../user-guide/pods.md) that contains the other master components.
 | 
			
		||||
@@ -94,15 +94,15 @@ This actually runs the kubelet, which in turn runs a [pod](../user-guide/pods.md
 | 
			
		||||
*Note, this could be combined with master above, but it requires --privileged for iptables manipulation*
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
docker run -d --net=host --privileged gcr.io/google_containers/hyperkube:v0.21.2 /hyperkube proxy --master=http://127.0.0.1:8080 --v=2
 | 
			
		||||
docker run -d --net=host --privileged gcr.io/google_containers/hyperkube:v1.0.1 /hyperkube proxy --master=http://127.0.0.1:8080 --v=2
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Test it out
 | 
			
		||||
 | 
			
		||||
At this point you should have a running Kubernetes cluster.  You can test this by downloading the kubectl 
 | 
			
		||||
binary
 | 
			
		||||
([OS X](https://storage.googleapis.com/kubernetes-release/release/v0.18.2/bin/darwin/amd64/kubectl))
 | 
			
		||||
([linux](https://storage.googleapis.com/kubernetes-release/release/v0.18.2/bin/linux/amd64/kubectl))
 | 
			
		||||
([OS X](https://storage.googleapis.com/kubernetes-release/release/v1.0.1/bin/darwin/amd64/kubectl))
 | 
			
		||||
([linux](https://storage.googleapis.com/kubernetes-release/release/v1.0.1/bin/linux/amd64/kubectl))
 | 
			
		||||
 | 
			
		||||
*Note:*
 | 
			
		||||
On OS/X you will need to set up port forwarding via ssh:
 | 
			
		||||
@@ -129,7 +129,7 @@ If you are running different Kubernetes clusters, you may need to specify `-s ht
 | 
			
		||||
### Run an application
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
kubectl -s http://localhost:8080 run-container nginx --image=nginx --port=80
 | 
			
		||||
kubectl -s http://localhost:8080 run nginx --image=nginx --port=80
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
now run `docker ps` you should see nginx running.  You may need to wait a few minutes for the image to get pulled.
 | 
			
		||||
@@ -144,7 +144,7 @@ This should print:
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
NAME      LABELS    SELECTOR              IP          PORT(S)
 | 
			
		||||
nginx     <none>    run=nginx             <ip-addr>   80/TCP
 | 
			
		||||
nginx     run=nginx run=nginx             <ip-addr>   80/TCP
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If ip-addr is blank run the following command to obtain it. Know issue #10836
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 | 
			
		||||
.SH NAME
 | 
			
		||||
.PP
 | 
			
		||||
kubectl stop \- Gracefully shut down a resource by name or filename.
 | 
			
		||||
kubectl stop \- Deprecated: Gracefully shut down a resource by name or filename.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.SH SYNOPSIS
 | 
			
		||||
@@ -13,7 +13,11 @@ kubectl stop \- Gracefully shut down a resource by name or filename.
 | 
			
		||||
 | 
			
		||||
.SH DESCRIPTION
 | 
			
		||||
.PP
 | 
			
		||||
Gracefully shut down a resource by name or filename.
 | 
			
		||||
Deprecated: Gracefully shut down a resource by name or filename.
 | 
			
		||||
 | 
			
		||||
.PP
 | 
			
		||||
stop command is deprecated, all its functionalities are covered by delete command.
 | 
			
		||||
See 'kubectl delete \-\-help' for more details.
 | 
			
		||||
 | 
			
		||||
.PP
 | 
			
		||||
Attempts to shut down and delete a resource that supports graceful termination.
 | 
			
		||||
 
 | 
			
		||||
@@ -97,10 +97,10 @@ kubectl
 | 
			
		||||
* [kubectl rolling-update](kubectl_rolling-update.md)	 - Perform a rolling update of the given ReplicationController.
 | 
			
		||||
* [kubectl run](kubectl_run.md)	 - Run a particular image on the cluster.
 | 
			
		||||
* [kubectl scale](kubectl_scale.md)	 - Set a new size for a Replication Controller.
 | 
			
		||||
* [kubectl stop](kubectl_stop.md)	 - Gracefully shut down a resource by name or filename.
 | 
			
		||||
* [kubectl stop](kubectl_stop.md)	 - Deprecated: Gracefully shut down a resource by name or filename.
 | 
			
		||||
* [kubectl version](kubectl_version.md)	 - Print the client and server version information.
 | 
			
		||||
 | 
			
		||||
###### Auto generated by spf13/cobra at 2015-07-14 00:11:42.96000791 +0000 UTC
 | 
			
		||||
###### Auto generated by spf13/cobra at 2015-07-29 09:18:59.541696918 +0000 UTC
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
 
 | 
			
		||||
@@ -33,12 +33,15 @@ Documentation for other releases can be found at
 | 
			
		||||
 | 
			
		||||
## kubectl stop
 | 
			
		||||
 | 
			
		||||
Gracefully shut down a resource by name or filename.
 | 
			
		||||
Deprecated: Gracefully shut down a resource by name or filename.
 | 
			
		||||
 | 
			
		||||
### Synopsis
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Gracefully shut down a resource by name or filename.
 | 
			
		||||
Deprecated: Gracefully shut down a resource by name or filename.
 | 
			
		||||
 | 
			
		||||
stop command is deprecated, all its functionalities are covered by delete command.
 | 
			
		||||
See 'kubectl delete --help' for more details.
 | 
			
		||||
 | 
			
		||||
Attempts to shut down and delete a resource that supports graceful termination.
 | 
			
		||||
If the resource is scalable it will be scaled to 0 before deletion.
 | 
			
		||||
@@ -109,7 +112,7 @@ $ kubectl stop -f path/to/resources
 | 
			
		||||
 | 
			
		||||
* [kubectl](kubectl.md)	 - kubectl controls the Kubernetes cluster manager
 | 
			
		||||
 | 
			
		||||
###### Auto generated by spf13/cobra at 2015-07-14 00:11:42.957441942 +0000 UTC
 | 
			
		||||
###### Auto generated by spf13/cobra at 2015-07-29 09:18:59.539597953 +0000 UTC
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
 
 | 
			
		||||
@@ -317,6 +317,7 @@ func TestExampleObjectSchemas(t *testing.T) {
 | 
			
		||||
			"spark-master-service":    &api.Service{},
 | 
			
		||||
			"spark-master":            &api.Pod{},
 | 
			
		||||
			"spark-worker-controller": &api.ReplicationController{},
 | 
			
		||||
			"spark-driver":            &api.Pod{},
 | 
			
		||||
		},
 | 
			
		||||
		"../examples/storm": {
 | 
			
		||||
			"storm-nimbus-service":    &api.Service{},
 | 
			
		||||
 
 | 
			
		||||
@@ -144,44 +144,35 @@ $ kubectl logs spark-master
 | 
			
		||||
15/06/26 14:15:55 INFO Master: Registering worker 10.244.0.19:60970 with 1 cores, 2.6 GB RAM
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Step Three: Do something with the cluster
 | 
			
		||||
## Step Three: Start your Spark driver to launch jobs on your Spark cluster
 | 
			
		||||
 | 
			
		||||
Get the address and port of the Master service.
 | 
			
		||||
The Spark driver is used to launch jobs into Spark cluster. You can read more about it in
 | 
			
		||||
[Spark architecture](http://spark.apache.org/docs/latest/cluster-overview.html).
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
$ kubectl get service spark-master
 | 
			
		||||
NAME           LABELS              SELECTOR            IP(S)          PORT(S)
 | 
			
		||||
spark-master   name=spark-master   name=spark-master   10.0.204.187   7077/TCP
 | 
			
		||||
```shell
 | 
			
		||||
$ kubectl create -f examples/spark/spark-driver.json
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
SSH to one of your cluster nodes. On GCE/GKE you can either use [Developers Console](https://console.developers.google.com)
 | 
			
		||||
(more details [here](https://cloud.google.com/compute/docs/ssh-in-browser))
 | 
			
		||||
or run  `gcloud compute ssh <name>` where the name can be taken from `kubectl get nodes`
 | 
			
		||||
(more details [here](https://cloud.google.com/compute/docs/gcloud-compute/#connecting)).
 | 
			
		||||
The Spark driver needs the Master service to be running.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ kubectl get nodes
 | 
			
		||||
NAME                     LABELS                                          STATUS
 | 
			
		||||
kubernetes-minion-5jvu   kubernetes.io/hostname=kubernetes-minion-5jvu   Ready
 | 
			
		||||
kubernetes-minion-6fbi   kubernetes.io/hostname=kubernetes-minion-6fbi   Ready
 | 
			
		||||
kubernetes-minion-8y2v   kubernetes.io/hostname=kubernetes-minion-8y2v   Ready
 | 
			
		||||
kubernetes-minion-h0tr   kubernetes.io/hostname=kubernetes-minion-h0tr   Ready
 | 
			
		||||
### Check to see if the driver is running
 | 
			
		||||
 | 
			
		||||
$ gcloud compute ssh kubernetes-minion-5jvu --zone=us-central1-b
 | 
			
		||||
Linux kubernetes-minion-5jvu 3.16.0-0.bpo.4-amd64 #1 SMP Debian 3.16.7-ckt9-3~deb8u1~bpo70+1 (2015-04-27) x86_64
 | 
			
		||||
 | 
			
		||||
=== GCE Kubernetes node setup complete ===
 | 
			
		||||
 | 
			
		||||
me@kubernetes-minion-5jvu:~$
 | 
			
		||||
```shell
 | 
			
		||||
$ kubectl get pods
 | 
			
		||||
NAME                                           READY     REASON    RESTARTS   AGE
 | 
			
		||||
[...]
 | 
			
		||||
spark-master                                    1/1       Running   0          14m
 | 
			
		||||
spark-driver                                    1/1       Running   0          10m
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Once logged in run spark-base image. Inside of the image there is a script
 | 
			
		||||
that sets up the environment based on the provided IP and port of the Master.
 | 
			
		||||
## Step Four: Do something with the cluster
 | 
			
		||||
 | 
			
		||||
Use the kubectl exec to connect to Spark driver
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cluster-node $ sudo docker run -it gcr.io/google_containers/spark-base
 | 
			
		||||
root@f12a6fec45ce:/# . /setup_client.sh 10.0.204.187 7077
 | 
			
		||||
root@f12a6fec45ce:/# pyspark
 | 
			
		||||
$ kubectl exec spark-driver -it bash
 | 
			
		||||
root@spark-driver:/#
 | 
			
		||||
root@spark-driver:/# pyspark
 | 
			
		||||
Python 2.7.9 (default, Mar  1 2015, 12:57:24)
 | 
			
		||||
[GCC 4.9.2] on linux2
 | 
			
		||||
Type "help", "copyright", "credits" or "license" for more information.
 | 
			
		||||
@@ -201,7 +192,7 @@ SparkContext available as sc, HiveContext available as sqlContext.
 | 
			
		||||
 | 
			
		||||
## Result
 | 
			
		||||
 | 
			
		||||
You now have services, replication controllers, and pods for the Spark master and Spark workers.
 | 
			
		||||
You now have services, replication controllers, and pods for the Spark master , Spark driver and Spark workers.
 | 
			
		||||
You can take this example to the next step and start using the Apache Spark cluster
 | 
			
		||||
you just created, see [Spark documentation](https://spark.apache.org/documentation.html)
 | 
			
		||||
for more information.
 | 
			
		||||
@@ -216,6 +207,8 @@ Make sure the Master Pod is running (use: ```kubectl get pods```).
 | 
			
		||||
 | 
			
		||||
```kubectl create -f spark-worker-controller.json```
 | 
			
		||||
 | 
			
		||||
```kubectl create -f spark-driver.json```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
[]()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								examples/spark/images/driver/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/spark/images/driver/Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
FROM gcr.io/google_containers/spark-base
 | 
			
		||||
ADD start.sh /start.sh
 | 
			
		||||
ADD log4j.properties /opt/spark/conf/log4j.properties
 | 
			
		||||
CMD ["/start.sh"]
 | 
			
		||||
							
								
								
									
										37
									
								
								examples/spark/images/driver/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								examples/spark/images/driver/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
<!-- BEGIN MUNGE: UNVERSIONED_WARNING -->
 | 
			
		||||
 | 
			
		||||
<!-- BEGIN STRIP_FOR_RELEASE -->
 | 
			
		||||
 | 
			
		||||
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | 
			
		||||
     width="25" height="25">
 | 
			
		||||
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | 
			
		||||
     width="25" height="25">
 | 
			
		||||
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | 
			
		||||
     width="25" height="25">
 | 
			
		||||
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | 
			
		||||
     width="25" height="25">
 | 
			
		||||
<img src="http://kubernetes.io/img/warning.png" alt="WARNING"
 | 
			
		||||
     width="25" height="25">
 | 
			
		||||
 | 
			
		||||
<h2>PLEASE NOTE: This document applies to the HEAD of the source tree</h2>
 | 
			
		||||
 | 
			
		||||
If you are using a released version of Kubernetes, you should
 | 
			
		||||
refer to the docs that go with that version.
 | 
			
		||||
 | 
			
		||||
<strong>
 | 
			
		||||
The latest 1.0.x release of this document can be found
 | 
			
		||||
[here](http://releases.k8s.io/release-1.0/examples/spark/images/driver/README.md).
 | 
			
		||||
 | 
			
		||||
Documentation for other releases can be found at
 | 
			
		||||
[releases.k8s.io](http://releases.k8s.io).
 | 
			
		||||
</strong>
 | 
			
		||||
--
 | 
			
		||||
 | 
			
		||||
<!-- END STRIP_FOR_RELEASE -->
 | 
			
		||||
 | 
			
		||||
<!-- END MUNGE: UNVERSIONED_WARNING -->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
[]()
 | 
			
		||||
<!-- END MUNGE: GENERATED_ANALYTICS -->
 | 
			
		||||
							
								
								
									
										23
									
								
								examples/spark/images/driver/start.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								examples/spark/images/driver/start.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
echo "$SPARK_MASTER_SERVICE_HOST spark-master" >> /etc/hosts
 | 
			
		||||
echo "SPARK_LOCAL_HOSTNAME=$(hostname -i)" >> /opt/spark/conf/spark-env.sh
 | 
			
		||||
echo "MASTER=spark://spark-master:$SPARK_MASTER_SERVICE_PORT" >> /opt/spark/conf/spark-env.sh
 | 
			
		||||
 | 
			
		||||
while true; do
 | 
			
		||||
  sleep 100
 | 
			
		||||
done
 | 
			
		||||
							
								
								
									
										23
									
								
								examples/spark/spark-driver.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								examples/spark/spark-driver.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
{
 | 
			
		||||
  "kind": "Pod",
 | 
			
		||||
  "apiVersion": "v1",
 | 
			
		||||
  "metadata": {
 | 
			
		||||
    "name": "spark-driver",
 | 
			
		||||
    "labels": {
 | 
			
		||||
      "name": "spark-driver"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "spec": {
 | 
			
		||||
    "containers": [
 | 
			
		||||
      {
 | 
			
		||||
        "name": "spark-driver",
 | 
			
		||||
        "image": "gurvin/spark-driver",
 | 
			
		||||
        "resources": {
 | 
			
		||||
          "limits": {
 | 
			
		||||
            "cpu": "100m"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,481 +0,0 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# Copyright 2014 The Kubernetes Authors All rights reserved.
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
# Verifies that services and virtual IPs work.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO(wojtek-t): Remove this test once the following go tests are stable:
 | 
			
		||||
# - "should work after restarting kube-proxy"
 | 
			
		||||
# - "should work after restarting apiserver"
 | 
			
		||||
 | 
			
		||||
set -o errexit
 | 
			
		||||
set -o nounset
 | 
			
		||||
set -o pipefail
 | 
			
		||||
 | 
			
		||||
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
 | 
			
		||||
 | 
			
		||||
: ${KUBE_VERSION_ROOT:=${KUBE_ROOT}}
 | 
			
		||||
: ${KUBECTL:="${KUBE_VERSION_ROOT}/cluster/kubectl.sh"}
 | 
			
		||||
: ${KUBE_CONFIG_FILE:="config-test.sh"}
 | 
			
		||||
 | 
			
		||||
export KUBECTL KUBE_CONFIG_FILE
 | 
			
		||||
 | 
			
		||||
TEST_NAMESPACE="services-test-${RANDOM}"
 | 
			
		||||
KUBECTL="${KUBECTL} --namespace=${TEST_NAMESPACE}"
 | 
			
		||||
 | 
			
		||||
source "${KUBE_ROOT}/cluster/kube-env.sh"
 | 
			
		||||
source "${KUBE_VERSION_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh"
 | 
			
		||||
 | 
			
		||||
prepare-e2e
 | 
			
		||||
 | 
			
		||||
function error() {
 | 
			
		||||
  echo "$@" >&2
 | 
			
		||||
  exit 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function sort_args() {
 | 
			
		||||
  [ $# == 0 ] && return
 | 
			
		||||
  a=($(printf "%s\n" "$@" | sort -n))
 | 
			
		||||
  echo "${a[*]}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Join args $2... with $1 between them.
 | 
			
		||||
# Example: join ", " x y z   =>   x, y, z
 | 
			
		||||
function join() {
 | 
			
		||||
  local sep item
 | 
			
		||||
  sep=$1
 | 
			
		||||
  shift
 | 
			
		||||
  echo -n "${1:-}"
 | 
			
		||||
  shift
 | 
			
		||||
  for item; do
 | 
			
		||||
    echo -n "${sep}${item}"
 | 
			
		||||
  done
 | 
			
		||||
  echo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
svcs_to_clean=()
 | 
			
		||||
function do_teardown() {
 | 
			
		||||
  ${KUBECTL} delete namespace "${TEST_NAMESPACE}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function make_namespace() {
 | 
			
		||||
  echo "Making namespace '${TEST_NAMESPACE}'"
 | 
			
		||||
  ${KUBECTL} create -f - << __EOF__
 | 
			
		||||
{
 | 
			
		||||
    "kind": "Namespace",
 | 
			
		||||
    "apiVersion": "v1",
 | 
			
		||||
    "metadata": {
 | 
			
		||||
        "name": "${TEST_NAMESPACE}"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
__EOF__
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
wait_for_apiserver() {
 | 
			
		||||
  echo "Waiting for apiserver to be up"
 | 
			
		||||
 | 
			
		||||
  local i
 | 
			
		||||
  for i in $(seq 1 12); do
 | 
			
		||||
    results=$(ssh-to-node "${master}" "
 | 
			
		||||
      wget -q -T 1 -O - http://localhost:8080/healthz || true
 | 
			
		||||
    ")
 | 
			
		||||
    if [[ "${results}" == "ok" ]]; then
 | 
			
		||||
      return
 | 
			
		||||
    fi
 | 
			
		||||
    sleep 5  # wait for apiserver to restart
 | 
			
		||||
  done
 | 
			
		||||
  error "restarting apiserver timed out"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
#   $2: service port
 | 
			
		||||
#   $3: service replica count
 | 
			
		||||
function start_service() {
 | 
			
		||||
  echo "Starting service '${TEST_NAMESPACE}/$1' on port $2 with $3 replicas"
 | 
			
		||||
  svcs_to_clean+=("$1")
 | 
			
		||||
  ${KUBECTL} create -f - << __EOF__
 | 
			
		||||
{
 | 
			
		||||
  "kind": "ReplicationController",
 | 
			
		||||
  "apiVersion": "v1",
 | 
			
		||||
  "metadata": {
 | 
			
		||||
    "name": "$1",
 | 
			
		||||
    "labels": {
 | 
			
		||||
      "name": "$1"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "spec": {
 | 
			
		||||
    "replicas": $3,
 | 
			
		||||
    "selector": {
 | 
			
		||||
      "name": "$1"
 | 
			
		||||
    },
 | 
			
		||||
    "template": {
 | 
			
		||||
      "metadata": {
 | 
			
		||||
        "labels": {
 | 
			
		||||
          "name": "$1"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "spec": {
 | 
			
		||||
        "containers": [
 | 
			
		||||
          {
 | 
			
		||||
            "name": "$1",
 | 
			
		||||
            "image": "gcr.io/google_containers/serve_hostname:1.1",
 | 
			
		||||
            "ports": [
 | 
			
		||||
              {
 | 
			
		||||
                "containerPort": 9376,
 | 
			
		||||
                "protocol": "TCP"
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
__EOF__
 | 
			
		||||
  ${KUBECTL} create -f - << __EOF__
 | 
			
		||||
{
 | 
			
		||||
  "kind": "Service",
 | 
			
		||||
  "apiVersion": "v1",
 | 
			
		||||
  "metadata": {
 | 
			
		||||
    "name": "$1",
 | 
			
		||||
    "labels": {
 | 
			
		||||
      "name": "$1"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "spec": {
 | 
			
		||||
    "ports": [
 | 
			
		||||
      {
 | 
			
		||||
        "protocol": "TCP",
 | 
			
		||||
        "port": $2,
 | 
			
		||||
        "targetPort": 9376
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "selector": {
 | 
			
		||||
      "name": "$1"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
__EOF__
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
function stop_service() {
 | 
			
		||||
  echo "Stopping service '$1'"
 | 
			
		||||
  ${KUBECTL} stop rc "$1" || true
 | 
			
		||||
  ${KUBECTL} delete services "$1" || true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
#   $2: expected pod count
 | 
			
		||||
function query_pods() {
 | 
			
		||||
  # This fails very occasionally, so retry a bit.
 | 
			
		||||
  local pods_unsorted=()
 | 
			
		||||
  local i
 | 
			
		||||
  for i in $(seq 1 10); do
 | 
			
		||||
    pods_unsorted=($(${KUBECTL} get pods -o template \
 | 
			
		||||
        '--template={{range.items}}{{.metadata.name}} {{end}}' \
 | 
			
		||||
        '--api-version=v1' \
 | 
			
		||||
        -l name="$1"))
 | 
			
		||||
    found="${#pods_unsorted[*]}"
 | 
			
		||||
    if [[ "${found}" == "$2" ]]; then
 | 
			
		||||
      break
 | 
			
		||||
    fi
 | 
			
		||||
    sleep 3
 | 
			
		||||
  done
 | 
			
		||||
  if [[ "${found}" != "$2" ]]; then
 | 
			
		||||
    error "Failed to query pods for $1: expected $2, found ${found}"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # The "return" is a sorted list of pod IDs.
 | 
			
		||||
  sort_args "${pods_unsorted[@]}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
#   $2: pod count
 | 
			
		||||
function wait_for_pods() {
 | 
			
		||||
  echo "Querying pods in $1"
 | 
			
		||||
  local pods_sorted=$(query_pods "$1" "$2")
 | 
			
		||||
  printf '\t%s\n' ${pods_sorted}
 | 
			
		||||
 | 
			
		||||
  # Container turn up on a clean cluster can take a while for the docker image
 | 
			
		||||
  # pulls.  Wait a generous amount of time.
 | 
			
		||||
  # TODO: Sometimes pods change underneath us, which makes the GET fail (404).
 | 
			
		||||
  # Maybe this test can be loosened and still be useful?
 | 
			
		||||
  pods_needed=$2
 | 
			
		||||
  local i
 | 
			
		||||
  for i in $(seq 1 30); do
 | 
			
		||||
    echo "Waiting for ${pods_needed} pods to become 'running'"
 | 
			
		||||
    pods_needed="$2"
 | 
			
		||||
    for id in ${pods_sorted}; do
 | 
			
		||||
      status=$(${KUBECTL} get pods "${id}" -o template --template='{{.status.phase}}' --api-version=v1)
 | 
			
		||||
      if [[ "${status}" == "Running" ]]; then
 | 
			
		||||
        pods_needed=$((pods_needed-1))
 | 
			
		||||
      fi
 | 
			
		||||
    done
 | 
			
		||||
    if [[ "${pods_needed}" == 0 ]]; then
 | 
			
		||||
      break
 | 
			
		||||
    fi
 | 
			
		||||
    sleep 3
 | 
			
		||||
  done
 | 
			
		||||
  if [[ "${pods_needed}" -gt 0 ]]; then
 | 
			
		||||
    error "Pods for $1 did not come up in time"
 | 
			
		||||
  fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
#   $2: service IP
 | 
			
		||||
#   $3: service port
 | 
			
		||||
#   $4: pod count
 | 
			
		||||
#   $5: pod IDs (sorted)
 | 
			
		||||
function wait_for_service_up() {
 | 
			
		||||
  local i
 | 
			
		||||
  local found_pods
 | 
			
		||||
  echo "waiting for $1 at $2:$3"
 | 
			
		||||
  # TODO: Reduce this interval once we have a sense for the latency distribution.
 | 
			
		||||
  for i in $(seq 1 10); do
 | 
			
		||||
    results=($(ssh-to-node "${test_node}" "
 | 
			
		||||
        set -e;
 | 
			
		||||
        for i in $(seq -s' ' 1 $(($4*3))); do
 | 
			
		||||
          wget -q -T 1 -O - http://$2:$3 || true;
 | 
			
		||||
          echo;
 | 
			
		||||
        done | sort -n | uniq
 | 
			
		||||
        "))
 | 
			
		||||
 | 
			
		||||
    found_pods=$(sort_args "${results[@]:+${results[@]}}")
 | 
			
		||||
    if [[ "${found_pods}" == "$5" ]]; then
 | 
			
		||||
      return
 | 
			
		||||
    fi
 | 
			
		||||
    echo "expected '$5', got '${found_pods}': will try again"
 | 
			
		||||
    sleep 5  # wait for endpoints to propagate
 | 
			
		||||
  done
 | 
			
		||||
  error "$1: failed to verify portal from host"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
#   $2: service IP
 | 
			
		||||
#   $3: service port
 | 
			
		||||
function wait_for_service_down() {
 | 
			
		||||
  local i
 | 
			
		||||
  for i in $(seq 1 15); do
 | 
			
		||||
    $(ssh-to-node "${test_node}" "
 | 
			
		||||
        curl -s --connect-timeout 2 "http://$2:$3" >/dev/null 2>&1 && exit 1 || exit 0;
 | 
			
		||||
        ") && break
 | 
			
		||||
    echo "Waiting for $1 to go down"
 | 
			
		||||
    sleep 2
 | 
			
		||||
  done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Args:
 | 
			
		||||
#   $1: service name
 | 
			
		||||
#   $2: service IP
 | 
			
		||||
#   $3: service port
 | 
			
		||||
#   $4: pod count
 | 
			
		||||
#   $5: pod IDs (sorted)
 | 
			
		||||
function verify_from_container() {
 | 
			
		||||
  local i
 | 
			
		||||
  local found_pods
 | 
			
		||||
  echo "waiting for $1 at $2:$3"
 | 
			
		||||
  # TODO: Reduce this interval once we have a sense for the latency distribution.
 | 
			
		||||
  for i in $(seq 1 10); do
 | 
			
		||||
    results=($(ssh-to-node "${test_node}" "
 | 
			
		||||
        set -e;
 | 
			
		||||
        sudo docker pull gcr.io/google_containers/busybox >/dev/null;
 | 
			
		||||
        sudo docker run gcr.io/google_containers/busybox sh -c '
 | 
			
		||||
            for i in $(seq -s' ' 1 $(($4*3))); do
 | 
			
		||||
              wget -q -T 1 -O - http://$2:$3 || true;
 | 
			
		||||
              echo;
 | 
			
		||||
            done
 | 
			
		||||
        '" | sort -n | uniq))
 | 
			
		||||
 | 
			
		||||
    found_pods=$(sort_args "${results[@]:+${results[@]}}")
 | 
			
		||||
    if [[ "${found_pods}" == "$5" ]]; then
 | 
			
		||||
      return
 | 
			
		||||
    fi
 | 
			
		||||
    echo "expected '$5', got '${found_pods}': will try again"
 | 
			
		||||
    sleep 5  # wait for endpoints to propagate
 | 
			
		||||
  done
 | 
			
		||||
  error "$1: failed to verify portal from host"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
trap do_teardown EXIT
 | 
			
		||||
 | 
			
		||||
# Get node IP addresses and pick one as our test point.
 | 
			
		||||
detect-minions
 | 
			
		||||
test_node="${MINION_NAMES[0]}"
 | 
			
		||||
master="${MASTER_NAME}"
 | 
			
		||||
 | 
			
		||||
# Make our namespace
 | 
			
		||||
make_namespace
 | 
			
		||||
 | 
			
		||||
# Launch some pods and services.
 | 
			
		||||
svc1_name="service1"
 | 
			
		||||
svc1_port=80
 | 
			
		||||
svc1_count=3
 | 
			
		||||
start_service "${svc1_name}" "${svc1_port}" "${svc1_count}"
 | 
			
		||||
 | 
			
		||||
svc2_name="service2"
 | 
			
		||||
svc2_port=80
 | 
			
		||||
svc2_count=3
 | 
			
		||||
start_service "${svc2_name}" "${svc2_port}" "${svc2_count}"
 | 
			
		||||
 | 
			
		||||
# Wait for the pods to become "running".
 | 
			
		||||
wait_for_pods "${svc1_name}" "${svc1_count}"
 | 
			
		||||
wait_for_pods "${svc2_name}" "${svc2_count}"
 | 
			
		||||
 | 
			
		||||
# Get the sorted lists of pods.
 | 
			
		||||
svc1_pods=$(query_pods "${svc1_name}" "${svc1_count}")
 | 
			
		||||
svc2_pods=$(query_pods "${svc2_name}" "${svc2_count}")
 | 
			
		||||
 | 
			
		||||
# Get the VIP IPs.
 | 
			
		||||
svc1_ip=$(${KUBECTL} get services -o template '--template={{.spec.clusterIP}}' "${svc1_name}" --api-version=v1)
 | 
			
		||||
test -n "${svc1_ip}" || error "Service1 IP is blank"
 | 
			
		||||
svc2_ip=$(${KUBECTL} get services -o template '--template={{.spec.clusterIP}}' "${svc2_name}" --api-version=v1)
 | 
			
		||||
test -n "${svc2_ip}" || error "Service2 IP is blank"
 | 
			
		||||
if [[ "${svc1_ip}" == "${svc2_ip}" ]]; then
 | 
			
		||||
  error "VIPs conflict: ${svc1_ip}"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 1: Prove that the service VIP is alive.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 1: Prove that the service VIP is alive."
 | 
			
		||||
echo "Verifying the VIP from the host"
 | 
			
		||||
wait_for_service_up "${svc1_name}" "${svc1_ip}" "${svc1_port}" \
 | 
			
		||||
    "${svc1_count}" "${svc1_pods}"
 | 
			
		||||
wait_for_service_up "${svc2_name}" "${svc2_ip}" "${svc2_port}" \
 | 
			
		||||
    "${svc2_count}" "${svc2_pods}"
 | 
			
		||||
echo "Verifying the VIP from a container"
 | 
			
		||||
verify_from_container "${svc1_name}" "${svc1_ip}" "${svc1_port}" \
 | 
			
		||||
    "${svc1_count}" "${svc1_pods}"
 | 
			
		||||
verify_from_container "${svc2_name}" "${svc2_ip}" "${svc2_port}" \
 | 
			
		||||
    "${svc2_count}" "${svc2_pods}"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 2: Bounce the proxy and make sure the VIP comes back.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 2: Bounce the proxy and make sure the VIP comes back."
 | 
			
		||||
echo "Restarting kube-proxy"
 | 
			
		||||
restart-kube-proxy "${test_node}"
 | 
			
		||||
echo "Verifying the VIP from the host"
 | 
			
		||||
wait_for_service_up "${svc1_name}" "${svc1_ip}" "${svc1_port}" \
 | 
			
		||||
    "${svc1_count}" "${svc1_pods}"
 | 
			
		||||
wait_for_service_up "${svc2_name}" "${svc2_ip}" "${svc2_port}" \
 | 
			
		||||
    "${svc2_count}" "${svc2_pods}"
 | 
			
		||||
echo "Verifying the VIP from a container"
 | 
			
		||||
verify_from_container "${svc1_name}" "${svc1_ip}" "${svc1_port}" \
 | 
			
		||||
    "${svc1_count}" "${svc1_pods}"
 | 
			
		||||
verify_from_container "${svc2_name}" "${svc2_ip}" "${svc2_port}" \
 | 
			
		||||
    "${svc2_count}" "${svc2_pods}"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 3: Stop one service and make sure it is gone.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 3: Stop one service and make sure it is gone."
 | 
			
		||||
stop_service "${svc1_name}"
 | 
			
		||||
wait_for_service_down "${svc1_name}" "${svc1_ip}" "${svc1_port}"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 4: Bring up another service.
 | 
			
		||||
# TODO: Actually add a test to force re-use.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 4: Bring up another service."
 | 
			
		||||
svc3_name="service3"
 | 
			
		||||
svc3_port=80
 | 
			
		||||
svc3_count=3
 | 
			
		||||
start_service "${svc3_name}" "${svc3_port}" "${svc3_count}"
 | 
			
		||||
 | 
			
		||||
# Wait for the pods to become "running".
 | 
			
		||||
wait_for_pods "${svc3_name}" "${svc3_count}"
 | 
			
		||||
 | 
			
		||||
# Get the sorted lists of pods.
 | 
			
		||||
svc3_pods=$(query_pods "${svc3_name}" "${svc3_count}")
 | 
			
		||||
 | 
			
		||||
# Get the VIP.
 | 
			
		||||
svc3_ip=$(${KUBECTL} get services -o template '--template={{.spec.clusterIP}}' "${svc3_name}" --api-version=v1)
 | 
			
		||||
test -n "${svc3_ip}" || error "Service3 IP is blank"
 | 
			
		||||
 | 
			
		||||
echo "Verifying the VIPs from the host"
 | 
			
		||||
wait_for_service_up "${svc3_name}" "${svc3_ip}" "${svc3_port}" \
 | 
			
		||||
    "${svc3_count}" "${svc3_pods}"
 | 
			
		||||
echo "Verifying the VIPs from a container"
 | 
			
		||||
verify_from_container "${svc3_name}" "${svc3_ip}" "${svc3_port}" \
 | 
			
		||||
    "${svc3_count}" "${svc3_pods}"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 5: Remove the iptables rules, make sure they come back.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 5: Remove the iptables rules, make sure they come back."
 | 
			
		||||
echo "Manually removing iptables rules"
 | 
			
		||||
# Remove both the new and old style chains, in case we're testing on an old kubelet
 | 
			
		||||
ssh-to-node "${test_node}" "sudo iptables -t nat -F KUBE-PORTALS-HOST || true"
 | 
			
		||||
ssh-to-node "${test_node}" "sudo iptables -t nat -F KUBE-PORTALS-CONTAINER || true"
 | 
			
		||||
echo "Verifying the VIPs from the host"
 | 
			
		||||
wait_for_service_up "${svc3_name}" "${svc3_ip}" "${svc3_port}" \
 | 
			
		||||
    "${svc3_count}" "${svc3_pods}"
 | 
			
		||||
echo "Verifying the VIPs from a container"
 | 
			
		||||
verify_from_container "${svc3_name}" "${svc3_ip}" "${svc3_port}" \
 | 
			
		||||
    "${svc3_count}" "${svc3_pods}"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 6: Restart the master, make sure VIPs come back.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 6: Restart the master, make sure VIPs come back."
 | 
			
		||||
echo "Restarting the master"
 | 
			
		||||
restart-apiserver "${master}"
 | 
			
		||||
wait_for_apiserver
 | 
			
		||||
echo "Verifying the VIPs from the host"
 | 
			
		||||
wait_for_service_up "${svc3_name}" "${svc3_ip}" "${svc3_port}" \
 | 
			
		||||
    "${svc3_count}" "${svc3_pods}"
 | 
			
		||||
echo "Verifying the VIPs from a container"
 | 
			
		||||
verify_from_container "${svc3_name}" "${svc3_ip}" "${svc3_port}" \
 | 
			
		||||
    "${svc3_count}" "${svc3_pods}"
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Test 7: Bring up another service, make sure it does not re-use IPs.
 | 
			
		||||
#
 | 
			
		||||
echo "Test 7: Bring up another service, make sure it does not re-use IPs."
 | 
			
		||||
svc4_name="service4"
 | 
			
		||||
svc4_port=80
 | 
			
		||||
svc4_count=3
 | 
			
		||||
start_service "${svc4_name}" "${svc4_port}" "${svc4_count}"
 | 
			
		||||
 | 
			
		||||
# Wait for the pods to become "running".
 | 
			
		||||
wait_for_pods "${svc4_name}" "${svc4_count}"
 | 
			
		||||
 | 
			
		||||
# Get the sorted lists of pods.
 | 
			
		||||
svc4_pods=$(query_pods "${svc4_name}" "${svc4_count}")
 | 
			
		||||
 | 
			
		||||
# Get the VIP.
 | 
			
		||||
svc4_ip=$(${KUBECTL} get services -o template '--template={{.spec.clusterIP}}' "${svc4_name}" --api-version=v1)
 | 
			
		||||
test -n "${svc4_ip}" || error "Service4 IP is blank"
 | 
			
		||||
if [[ "${svc4_ip}" == "${svc2_ip}" || "${svc4_ip}" == "${svc3_ip}" ]]; then
 | 
			
		||||
  error "VIPs conflict: ${svc4_ip}"
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
echo "Verifying the VIPs from the host"
 | 
			
		||||
wait_for_service_up "${svc4_name}" "${svc4_ip}" "${svc4_port}" \
 | 
			
		||||
    "${svc4_count}" "${svc4_pods}"
 | 
			
		||||
echo "Verifying the VIPs from a container"
 | 
			
		||||
verify_from_container "${svc4_name}" "${svc4_ip}" "${svc4_port}" \
 | 
			
		||||
    "${svc4_count}" "${svc4_pods}"
 | 
			
		||||
 | 
			
		||||
exit 0
 | 
			
		||||
@@ -87,7 +87,6 @@ readonly KUBE_TEST_PORTABLE=(
 | 
			
		||||
  contrib/for-tests/network-tester/rc.json
 | 
			
		||||
  contrib/for-tests/network-tester/service.json
 | 
			
		||||
  hack/e2e.go
 | 
			
		||||
  hack/e2e-suite
 | 
			
		||||
  hack/e2e-internal
 | 
			
		||||
  hack/ginkgo-e2e.sh
 | 
			
		||||
  hack/lib
 | 
			
		||||
@@ -104,10 +103,16 @@ readonly KUBE_CLIENT_PLATFORMS=(
 | 
			
		||||
  windows/amd64
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Gigabytes desired for parallel platform builds. 8 is fairly
 | 
			
		||||
# Gigabytes desired for parallel platform builds. 11 is fairly
 | 
			
		||||
# arbitrary, but is a reasonable splitting point for 2015
 | 
			
		||||
# laptops-versus-not.
 | 
			
		||||
readonly KUBE_PARALLEL_BUILD_MEMORY=8
 | 
			
		||||
#
 | 
			
		||||
# If you are using boot2docker, the following seems to work (note 
 | 
			
		||||
# that 12000 rounds to 11G):
 | 
			
		||||
#   boot2docker down
 | 
			
		||||
#   VBoxManage modifyvm boot2docker-vm --memory 12000
 | 
			
		||||
#   boot2docker up
 | 
			
		||||
readonly KUBE_PARALLEL_BUILD_MEMORY=11
 | 
			
		||||
 | 
			
		||||
readonly KUBE_ALL_TARGETS=(
 | 
			
		||||
  "${KUBE_SERVER_TARGETS[@]}"
 | 
			
		||||
 
 | 
			
		||||
@@ -33,14 +33,6 @@ function generate_version() {
 | 
			
		||||
	cat >> $TMPFILE <<EOF
 | 
			
		||||
package ${version}
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AUTO-GENERATED FUNCTIONS START HERE
 | 
			
		||||
EOF
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,14 +18,14 @@ package api
 | 
			
		||||
 | 
			
		||||
// AUTO-GENERATED FUNCTIONS START HERE
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
	"speter.net/go/exp/math/dec/inf"
 | 
			
		||||
	"time"
 | 
			
		||||
	resource "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	conversion "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
	fields "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
 | 
			
		||||
	labels "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
 | 
			
		||||
	runtime "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
			
		||||
	util "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
	inf "speter.net/go/exp/math/dec/inf"
 | 
			
		||||
	time "time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func deepCopy_api_AWSElasticBlockStoreVolumeSource(in AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, c *conversion.Cloner) error {
 | 
			
		||||
@@ -587,7 +587,7 @@ func deepCopy_api_LimitRange(in LimitRange, out *LimitRange, c *conversion.Clone
 | 
			
		||||
func deepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conversion.Cloner) error {
 | 
			
		||||
	out.Type = in.Type
 | 
			
		||||
	if in.Max != nil {
 | 
			
		||||
		out.Max = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Max = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Max {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -599,7 +599,7 @@ func deepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv
 | 
			
		||||
		out.Max = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Min != nil {
 | 
			
		||||
		out.Min = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Min = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Min {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -611,7 +611,7 @@ func deepCopy_api_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conv
 | 
			
		||||
		out.Min = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Default != nil {
 | 
			
		||||
		out.Default = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Default = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Default {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -857,7 +857,7 @@ func deepCopy_api_NodeSpec(in NodeSpec, out *NodeSpec, c *conversion.Cloner) err
 | 
			
		||||
 | 
			
		||||
func deepCopy_api_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1041,7 +1041,7 @@ func deepCopy_api_PersistentVolumeClaimStatus(in PersistentVolumeClaimStatus, ou
 | 
			
		||||
		out.AccessModes = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1143,7 +1143,7 @@ func deepCopy_api_PersistentVolumeSource(in PersistentVolumeSource, out *Persist
 | 
			
		||||
 | 
			
		||||
func deepCopy_api_PersistentVolumeSpec(in PersistentVolumeSpec, out *PersistentVolumeSpec, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1571,7 +1571,7 @@ func deepCopy_api_ResourceQuotaList(in ResourceQuotaList, out *ResourceQuotaList
 | 
			
		||||
 | 
			
		||||
func deepCopy_api_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1587,7 +1587,7 @@ func deepCopy_api_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec
 | 
			
		||||
 | 
			
		||||
func deepCopy_api_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaStatus, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1599,7 +1599,7 @@ func deepCopy_api_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuota
 | 
			
		||||
		out.Hard = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Used != nil {
 | 
			
		||||
		out.Used = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Used = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Used {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1615,7 +1615,7 @@ func deepCopy_api_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuota
 | 
			
		||||
 | 
			
		||||
func deepCopy_api_ResourceRequirements(in ResourceRequirements, out *ResourceRequirements, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Limits != nil {
 | 
			
		||||
		out.Limits = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Limits = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Limits {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1627,7 +1627,7 @@ func deepCopy_api_ResourceRequirements(in ResourceRequirements, out *ResourceReq
 | 
			
		||||
		out.Limits = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Requests != nil {
 | 
			
		||||
		out.Requests = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Requests = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Requests {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,15 +16,14 @@ limitations under the License.
 | 
			
		||||
 | 
			
		||||
package v1
 | 
			
		||||
 | 
			
		||||
// AUTO-GENERATED FUNCTIONS START HERE
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
	api "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	resource "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	conversion "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
	reflect "reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AUTO-GENERATED FUNCTIONS START HERE
 | 
			
		||||
func convert_api_AWSElasticBlockStoreVolumeSource_To_v1_AWSElasticBlockStoreVolumeSource(in *api.AWSElasticBlockStoreVolumeSource, out *AWSElasticBlockStoreVolumeSource, s conversion.Scope) error {
 | 
			
		||||
	if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
 | 
			
		||||
		defaulting.(func(*api.AWSElasticBlockStoreVolumeSource))(in)
 | 
			
		||||
@@ -692,7 +691,7 @@ func convert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out
 | 
			
		||||
	}
 | 
			
		||||
	out.Type = LimitType(in.Type)
 | 
			
		||||
	if in.Max != nil {
 | 
			
		||||
		out.Max = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Max = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Max {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -704,7 +703,7 @@ func convert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out
 | 
			
		||||
		out.Max = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Min != nil {
 | 
			
		||||
		out.Min = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Min = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Min {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -716,7 +715,7 @@ func convert_api_LimitRangeItem_To_v1_LimitRangeItem(in *api.LimitRangeItem, out
 | 
			
		||||
		out.Min = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Default != nil {
 | 
			
		||||
		out.Default = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Default = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Default {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1006,7 +1005,7 @@ func convert_api_NodeStatus_To_v1_NodeStatus(in *api.NodeStatus, out *NodeStatus
 | 
			
		||||
		defaulting.(func(*api.NodeStatus))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1216,7 +1215,7 @@ func convert_api_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(i
 | 
			
		||||
		out.AccessModes = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1330,7 +1329,7 @@ func convert_api_PersistentVolumeSpec_To_v1_PersistentVolumeSpec(in *api.Persist
 | 
			
		||||
		defaulting.(func(*api.PersistentVolumeSpec))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1735,7 +1734,7 @@ func convert_api_ResourceQuotaSpec_To_v1_ResourceQuotaSpec(in *api.ResourceQuota
 | 
			
		||||
		defaulting.(func(*api.ResourceQuotaSpec))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1754,7 +1753,7 @@ func convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQ
 | 
			
		||||
		defaulting.(func(*api.ResourceQuotaStatus))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1766,7 +1765,7 @@ func convert_api_ResourceQuotaStatus_To_v1_ResourceQuotaStatus(in *api.ResourceQ
 | 
			
		||||
		out.Hard = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Used != nil {
 | 
			
		||||
		out.Used = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Used = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Used {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1785,7 +1784,7 @@ func convert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.Resourc
 | 
			
		||||
		defaulting.(func(*api.ResourceRequirements))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Limits != nil {
 | 
			
		||||
		out.Limits = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Limits = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Limits {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -1797,7 +1796,7 @@ func convert_api_ResourceRequirements_To_v1_ResourceRequirements(in *api.Resourc
 | 
			
		||||
		out.Limits = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Requests != nil {
 | 
			
		||||
		out.Requests = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Requests = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Requests {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -2942,7 +2941,7 @@ func convert_v1_LimitRangeItem_To_api_LimitRangeItem(in *LimitRangeItem, out *ap
 | 
			
		||||
	}
 | 
			
		||||
	out.Type = api.LimitType(in.Type)
 | 
			
		||||
	if in.Max != nil {
 | 
			
		||||
		out.Max = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Max = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Max {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -2954,7 +2953,7 @@ func convert_v1_LimitRangeItem_To_api_LimitRangeItem(in *LimitRangeItem, out *ap
 | 
			
		||||
		out.Max = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Min != nil {
 | 
			
		||||
		out.Min = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Min = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Min {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -2966,7 +2965,7 @@ func convert_v1_LimitRangeItem_To_api_LimitRangeItem(in *LimitRangeItem, out *ap
 | 
			
		||||
		out.Min = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Default != nil {
 | 
			
		||||
		out.Default = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Default = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Default {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -3256,7 +3255,7 @@ func convert_v1_NodeStatus_To_api_NodeStatus(in *NodeStatus, out *api.NodeStatus
 | 
			
		||||
		defaulting.(func(*NodeStatus))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -3466,7 +3465,7 @@ func convert_v1_PersistentVolumeClaimStatus_To_api_PersistentVolumeClaimStatus(i
 | 
			
		||||
		out.AccessModes = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -3580,7 +3579,7 @@ func convert_v1_PersistentVolumeSpec_To_api_PersistentVolumeSpec(in *PersistentV
 | 
			
		||||
		defaulting.(func(*PersistentVolumeSpec))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -3985,7 +3984,7 @@ func convert_v1_ResourceQuotaSpec_To_api_ResourceQuotaSpec(in *ResourceQuotaSpec
 | 
			
		||||
		defaulting.(func(*ResourceQuotaSpec))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -4004,7 +4003,7 @@ func convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in *ResourceQuota
 | 
			
		||||
		defaulting.(func(*ResourceQuotaStatus))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -4016,7 +4015,7 @@ func convert_v1_ResourceQuotaStatus_To_api_ResourceQuotaStatus(in *ResourceQuota
 | 
			
		||||
		out.Hard = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Used != nil {
 | 
			
		||||
		out.Used = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Used = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Used {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -4035,7 +4034,7 @@ func convert_v1_ResourceRequirements_To_api_ResourceRequirements(in *ResourceReq
 | 
			
		||||
		defaulting.(func(*ResourceRequirements))(in)
 | 
			
		||||
	}
 | 
			
		||||
	if in.Limits != nil {
 | 
			
		||||
		out.Limits = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Limits = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Limits {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
@@ -4047,7 +4046,7 @@ func convert_v1_ResourceRequirements_To_api_ResourceRequirements(in *ResourceReq
 | 
			
		||||
		out.Limits = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Requests != nil {
 | 
			
		||||
		out.Requests = make(map[api.ResourceName]resource.Quantity)
 | 
			
		||||
		out.Requests = make(api.ResourceList)
 | 
			
		||||
		for key, val := range in.Requests {
 | 
			
		||||
			newVal := resource.Quantity{}
 | 
			
		||||
			if err := s.Convert(&val, &newVal, 0); err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,13 +18,13 @@ package v1
 | 
			
		||||
 | 
			
		||||
// AUTO-GENERATED FUNCTIONS START HERE
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
	"speter.net/go/exp/math/dec/inf"
 | 
			
		||||
	"time"
 | 
			
		||||
	api "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	resource "github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
 | 
			
		||||
	conversion "github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
 | 
			
		||||
	runtime "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
			
		||||
	util "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
			
		||||
	inf "speter.net/go/exp/math/dec/inf"
 | 
			
		||||
	time "time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func deepCopy_resource_Quantity(in resource.Quantity, out *resource.Quantity, c *conversion.Cloner) error {
 | 
			
		||||
@@ -600,7 +600,7 @@ func deepCopy_v1_LimitRange(in LimitRange, out *LimitRange, c *conversion.Cloner
 | 
			
		||||
func deepCopy_v1_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conversion.Cloner) error {
 | 
			
		||||
	out.Type = in.Type
 | 
			
		||||
	if in.Max != nil {
 | 
			
		||||
		out.Max = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Max = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Max {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -612,7 +612,7 @@ func deepCopy_v1_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conve
 | 
			
		||||
		out.Max = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Min != nil {
 | 
			
		||||
		out.Min = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Min = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Min {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -624,7 +624,7 @@ func deepCopy_v1_LimitRangeItem(in LimitRangeItem, out *LimitRangeItem, c *conve
 | 
			
		||||
		out.Min = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Default != nil {
 | 
			
		||||
		out.Default = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Default = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Default {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -860,7 +860,7 @@ func deepCopy_v1_NodeSpec(in NodeSpec, out *NodeSpec, c *conversion.Cloner) erro
 | 
			
		||||
 | 
			
		||||
func deepCopy_v1_NodeStatus(in NodeStatus, out *NodeStatus, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1044,7 +1044,7 @@ func deepCopy_v1_PersistentVolumeClaimStatus(in PersistentVolumeClaimStatus, out
 | 
			
		||||
		out.AccessModes = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1146,7 +1146,7 @@ func deepCopy_v1_PersistentVolumeSource(in PersistentVolumeSource, out *Persiste
 | 
			
		||||
 | 
			
		||||
func deepCopy_v1_PersistentVolumeSpec(in PersistentVolumeSpec, out *PersistentVolumeSpec, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Capacity != nil {
 | 
			
		||||
		out.Capacity = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Capacity = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Capacity {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1580,7 +1580,7 @@ func deepCopy_v1_ResourceQuotaList(in ResourceQuotaList, out *ResourceQuotaList,
 | 
			
		||||
 | 
			
		||||
func deepCopy_v1_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1596,7 +1596,7 @@ func deepCopy_v1_ResourceQuotaSpec(in ResourceQuotaSpec, out *ResourceQuotaSpec,
 | 
			
		||||
 | 
			
		||||
func deepCopy_v1_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaStatus, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Hard != nil {
 | 
			
		||||
		out.Hard = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Hard = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Hard {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1608,7 +1608,7 @@ func deepCopy_v1_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaS
 | 
			
		||||
		out.Hard = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Used != nil {
 | 
			
		||||
		out.Used = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Used = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Used {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1624,7 +1624,7 @@ func deepCopy_v1_ResourceQuotaStatus(in ResourceQuotaStatus, out *ResourceQuotaS
 | 
			
		||||
 | 
			
		||||
func deepCopy_v1_ResourceRequirements(in ResourceRequirements, out *ResourceRequirements, c *conversion.Cloner) error {
 | 
			
		||||
	if in.Limits != nil {
 | 
			
		||||
		out.Limits = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Limits = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Limits {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
@@ -1636,7 +1636,7 @@ func deepCopy_v1_ResourceRequirements(in ResourceRequirements, out *ResourceRequ
 | 
			
		||||
		out.Limits = nil
 | 
			
		||||
	}
 | 
			
		||||
	if in.Requests != nil {
 | 
			
		||||
		out.Requests = make(map[ResourceName]resource.Quantity)
 | 
			
		||||
		out.Requests = make(ResourceList)
 | 
			
		||||
		for key, val := range in.Requests {
 | 
			
		||||
			newVal := new(resource.Quantity)
 | 
			
		||||
			if err := deepCopy_resource_Quantity(val, newVal, c); err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -204,10 +204,11 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
	}
 | 
			
		||||
	versionedStatus := indirectArbitraryPointer(versionedStatusPtr)
 | 
			
		||||
	var (
 | 
			
		||||
		getOptions     runtime.Object
 | 
			
		||||
		getOptionsKind string
 | 
			
		||||
		getSubpath     bool
 | 
			
		||||
		getSubpathKey  string
 | 
			
		||||
		getOptions          runtime.Object
 | 
			
		||||
		versionedGetOptions runtime.Object
 | 
			
		||||
		getOptionsKind      string
 | 
			
		||||
		getSubpath          bool
 | 
			
		||||
		getSubpathKey       string
 | 
			
		||||
	)
 | 
			
		||||
	if isGetterWithOptions {
 | 
			
		||||
		getOptions, getSubpath, getSubpathKey = getterWithOptions.NewGetOptions()
 | 
			
		||||
@@ -215,14 +216,19 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		versionedGetOptions, err = a.group.Creater.New(serverVersion, getOptionsKind)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		isGetter = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		connectOptions     runtime.Object
 | 
			
		||||
		connectOptionsKind string
 | 
			
		||||
		connectSubpath     bool
 | 
			
		||||
		connectSubpathKey  string
 | 
			
		||||
		connectOptions          runtime.Object
 | 
			
		||||
		versionedConnectOptions runtime.Object
 | 
			
		||||
		connectOptionsKind      string
 | 
			
		||||
		connectSubpath          bool
 | 
			
		||||
		connectSubpathKey       string
 | 
			
		||||
	)
 | 
			
		||||
	if isConnecter {
 | 
			
		||||
		connectOptions, connectSubpath, connectSubpathKey = connecter.NewConnectOptions()
 | 
			
		||||
@@ -231,6 +237,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			versionedConnectOptions, err = a.group.Creater.New(serverVersion, connectOptionsKind)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -390,7 +397,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
				Returns(http.StatusOK, "OK", versionedObject).
 | 
			
		||||
				Writes(versionedObject)
 | 
			
		||||
			if isGetterWithOptions {
 | 
			
		||||
				if err := addObjectParams(ws, route, getOptions); err != nil {
 | 
			
		||||
				if err := addObjectParams(ws, route, versionedGetOptions); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -561,8 +568,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
 | 
			
		||||
					Produces("*/*").
 | 
			
		||||
					Consumes("*/*").
 | 
			
		||||
					Writes("string")
 | 
			
		||||
				if connectOptions != nil {
 | 
			
		||||
					if err := addObjectParams(ws, route, connectOptions); err != nil {
 | 
			
		||||
				if versionedConnectOptions != nil {
 | 
			
		||||
					if err := addObjectParams(ws, route, versionedConnectOptions); err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -255,8 +255,8 @@ func (*SimpleRoot) IsAnAPIObject() {}
 | 
			
		||||
 | 
			
		||||
type SimpleGetOptions struct {
 | 
			
		||||
	api.TypeMeta `json:",inline"`
 | 
			
		||||
	Param1       string `json:"param1"`
 | 
			
		||||
	Param2       string `json:"param2"`
 | 
			
		||||
	Param1       string `json:"param1" description:"description for param1"`
 | 
			
		||||
	Param2       string `json:"param2" description:"description for param2"`
 | 
			
		||||
	Path         string `json:"atAPath"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1078,6 +1078,47 @@ func TestGetBinary(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validateSimpleGetOptionsParams(t *testing.T, route *restful.Route) {
 | 
			
		||||
	// Validate name and description
 | 
			
		||||
	expectedParams := map[string]string{
 | 
			
		||||
		"param1":  "description for param1",
 | 
			
		||||
		"param2":  "description for param2",
 | 
			
		||||
		"atAPath": "",
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range route.ParameterDocs {
 | 
			
		||||
		data := p.Data()
 | 
			
		||||
		if desc, exists := expectedParams[data.Name]; exists {
 | 
			
		||||
			if desc != data.Description {
 | 
			
		||||
				t.Errorf("unexpected description for parameter %s: %s\n", data.Name, data.Description)
 | 
			
		||||
			}
 | 
			
		||||
			delete(expectedParams, data.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(expectedParams) > 0 {
 | 
			
		||||
		t.Errorf("did not find all expected parameters: %#v", expectedParams)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetWithOptionsRouteParams(t *testing.T) {
 | 
			
		||||
	storage := map[string]rest.Storage{}
 | 
			
		||||
	simpleStorage := GetWithOptionsRESTStorage{
 | 
			
		||||
		SimpleRESTStorage: &SimpleRESTStorage{},
 | 
			
		||||
	}
 | 
			
		||||
	storage["simple"] = &simpleStorage
 | 
			
		||||
	handler := handle(storage)
 | 
			
		||||
	ws := handler.(*defaultAPIServer).container.RegisteredWebServices()
 | 
			
		||||
	if len(ws) == 0 {
 | 
			
		||||
		t.Fatal("no web services registered")
 | 
			
		||||
	}
 | 
			
		||||
	routes := ws[0].Routes()
 | 
			
		||||
	for i := range routes {
 | 
			
		||||
		if routes[i].Method == "GET" && routes[i].Operation == "readNamespacedSimple" {
 | 
			
		||||
			validateSimpleGetOptionsParams(t, &routes[i])
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetWithOptions(t *testing.T) {
 | 
			
		||||
	storage := map[string]rest.Storage{}
 | 
			
		||||
	simpleStorage := GetWithOptionsRESTStorage{
 | 
			
		||||
@@ -1292,6 +1333,33 @@ func TestConnect(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestConnectWithOptionsRouteParams(t *testing.T) {
 | 
			
		||||
	connectStorage := &ConnecterRESTStorage{
 | 
			
		||||
		connectHandler:      &SimpleConnectHandler{},
 | 
			
		||||
		emptyConnectOptions: &SimpleGetOptions{},
 | 
			
		||||
	}
 | 
			
		||||
	storage := map[string]rest.Storage{
 | 
			
		||||
		"simple":         &SimpleRESTStorage{},
 | 
			
		||||
		"simple/connect": connectStorage,
 | 
			
		||||
	}
 | 
			
		||||
	handler := handle(storage)
 | 
			
		||||
	ws := handler.(*defaultAPIServer).container.RegisteredWebServices()
 | 
			
		||||
	if len(ws) == 0 {
 | 
			
		||||
		t.Fatal("no web services registered")
 | 
			
		||||
	}
 | 
			
		||||
	routes := ws[0].Routes()
 | 
			
		||||
	for i := range routes {
 | 
			
		||||
		switch routes[i].Operation {
 | 
			
		||||
		case "connectGetNamespacedSimpleConnect":
 | 
			
		||||
		case "connectPostNamespacedSimpleConnect":
 | 
			
		||||
		case "connectPutNamespacedSimpleConnect":
 | 
			
		||||
		case "connectDeleteNamespacedSimpleConnect":
 | 
			
		||||
			validateSimpleGetOptionsParams(t, &routes[i])
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestConnectWithOptions(t *testing.T) {
 | 
			
		||||
	responseText := "Hello World"
 | 
			
		||||
	itemID := "theID"
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,9 @@ type Capabilities struct {
 | 
			
		||||
 | 
			
		||||
	// List of pod sources for which using host network is allowed.
 | 
			
		||||
	HostNetworkSources []string
 | 
			
		||||
 | 
			
		||||
	// PerConnectionBandwidthLimitBytesPerSec limits the throughput of each connection (currently only used for proxy, exec, attach)
 | 
			
		||||
	PerConnectionBandwidthLimitBytesPerSec int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: Clean these up into a singleton
 | 
			
		||||
@@ -43,10 +46,11 @@ func Initialize(c Capabilities) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Setup the capability set.  It wraps Initialize for improving usibility.
 | 
			
		||||
func Setup(allowPrivileged bool, hostNetworkSources []string) {
 | 
			
		||||
func Setup(allowPrivileged bool, hostNetworkSources []string, perConnectionBytesPerSec int64) {
 | 
			
		||||
	Initialize(Capabilities{
 | 
			
		||||
		AllowPrivileged:    allowPrivileged,
 | 
			
		||||
		HostNetworkSources: hostNetworkSources,
 | 
			
		||||
		AllowPrivileged:                        allowPrivileged,
 | 
			
		||||
		HostNetworkSources:                     hostNetworkSources,
 | 
			
		||||
		PerConnectionBandwidthLimitBytesPerSec: perConnectionBytesPerSec,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								pkg/client/cache/reflector.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								pkg/client/cache/reflector.go
									
									
									
									
										vendored
									
									
								
							@@ -224,7 +224,7 @@ func (r *Reflector) listAndWatch(stopCh <-chan struct{}) {
 | 
			
		||||
		}
 | 
			
		||||
		if err := r.watchHandler(w, &resourceVersion, resyncCh, stopCh); err != nil {
 | 
			
		||||
			if err != errorResyncRequested && err != errorStopRequested {
 | 
			
		||||
				util.HandleError(fmt.Errorf("%s: watch of %v ended with: %v", r.name, r.expectedType, err))
 | 
			
		||||
				glog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedType, err)
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,10 +43,10 @@ func MinifyConfig(config *Config) error {
 | 
			
		||||
		return fmt.Errorf("cannot locate context %v", config.CurrentContext)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newContexts := map[string]Context{}
 | 
			
		||||
	newContexts := map[string]*Context{}
 | 
			
		||||
	newContexts[config.CurrentContext] = currContext
 | 
			
		||||
 | 
			
		||||
	newClusters := map[string]Cluster{}
 | 
			
		||||
	newClusters := map[string]*Cluster{}
 | 
			
		||||
	if len(currContext.Cluster) > 0 {
 | 
			
		||||
		if _, exists := config.Clusters[currContext.Cluster]; !exists {
 | 
			
		||||
			return fmt.Errorf("cannot locate cluster %v", currContext.Cluster)
 | 
			
		||||
@@ -55,7 +55,7 @@ func MinifyConfig(config *Config) error {
 | 
			
		||||
		newClusters[currContext.Cluster] = config.Clusters[currContext.Cluster]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newAuthInfos := map[string]AuthInfo{}
 | 
			
		||||
	newAuthInfos := map[string]*AuthInfo{}
 | 
			
		||||
	if len(currContext.AuthInfo) > 0 {
 | 
			
		||||
		if _, exists := config.AuthInfos[currContext.AuthInfo]; !exists {
 | 
			
		||||
			return fmt.Errorf("cannot locate user %v", currContext.AuthInfo)
 | 
			
		||||
 
 | 
			
		||||
@@ -38,13 +38,13 @@ func newMergedConfig(certFile, certContent, keyFile, keyContent, caFile, caConte
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return Config{
 | 
			
		||||
		AuthInfos: map[string]AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*AuthInfo{
 | 
			
		||||
			"red-user":  {Token: "red-token", ClientCertificateData: []byte(certContent), ClientKeyData: []byte(keyContent)},
 | 
			
		||||
			"blue-user": {Token: "blue-token", ClientCertificate: certFile, ClientKey: keyFile}},
 | 
			
		||||
		Clusters: map[string]Cluster{
 | 
			
		||||
		Clusters: map[string]*Cluster{
 | 
			
		||||
			"cow-cluster":     {Server: "http://cow.org:8080", CertificateAuthorityData: []byte(caContent)},
 | 
			
		||||
			"chicken-cluster": {Server: "http://chicken.org:8080", CertificateAuthority: caFile}},
 | 
			
		||||
		Contexts: map[string]Context{
 | 
			
		||||
		Contexts: map[string]*Context{
 | 
			
		||||
			"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"},
 | 
			
		||||
			"shaker-context":  {AuthInfo: "blue-user", Cluster: "chicken-cluster"}},
 | 
			
		||||
		CurrentContext: "federal-context",
 | 
			
		||||
 
 | 
			
		||||
@@ -33,21 +33,21 @@ type Config struct {
 | 
			
		||||
	// Preferences holds general information to be use for cli interactions
 | 
			
		||||
	Preferences Preferences `json:"preferences"`
 | 
			
		||||
	// Clusters is a map of referencable names to cluster configs
 | 
			
		||||
	Clusters map[string]Cluster `json:"clusters"`
 | 
			
		||||
	Clusters map[string]*Cluster `json:"clusters"`
 | 
			
		||||
	// AuthInfos is a map of referencable names to user configs
 | 
			
		||||
	AuthInfos map[string]AuthInfo `json:"users"`
 | 
			
		||||
	AuthInfos map[string]*AuthInfo `json:"users"`
 | 
			
		||||
	// Contexts is a map of referencable names to context configs
 | 
			
		||||
	Contexts map[string]Context `json:"contexts"`
 | 
			
		||||
	Contexts map[string]*Context `json:"contexts"`
 | 
			
		||||
	// CurrentContext is the name of the context that you would like to use by default
 | 
			
		||||
	CurrentContext string `json:"current-context"`
 | 
			
		||||
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
 | 
			
		||||
	Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
	Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Preferences struct {
 | 
			
		||||
	Colors bool `json:"colors,omitempty"`
 | 
			
		||||
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
 | 
			
		||||
	Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
	Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cluster contains information about how to communicate with a kubernetes cluster
 | 
			
		||||
@@ -65,7 +65,7 @@ type Cluster struct {
 | 
			
		||||
	// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
 | 
			
		||||
	CertificateAuthorityData []byte `json:"certificate-authority-data,omitempty"`
 | 
			
		||||
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
 | 
			
		||||
	Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
	Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AuthInfo contains information that describes identity information.  This is use to tell the kubernetes cluster who you are.
 | 
			
		||||
@@ -87,7 +87,7 @@ type AuthInfo struct {
 | 
			
		||||
	// Password is the password for basic authentication to the kubernetes cluster.
 | 
			
		||||
	Password string `json:"password,omitempty"`
 | 
			
		||||
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
 | 
			
		||||
	Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
	Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
 | 
			
		||||
@@ -101,36 +101,36 @@ type Context struct {
 | 
			
		||||
	// Namespace is the default namespace to use on unspecified requests
 | 
			
		||||
	Namespace string `json:"namespace,omitempty"`
 | 
			
		||||
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
 | 
			
		||||
	Extensions map[string]runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
	Extensions map[string]*runtime.EmbeddedObject `json:"extensions,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
 | 
			
		||||
func NewConfig() *Config {
 | 
			
		||||
	return &Config{
 | 
			
		||||
		Preferences: *NewPreferences(),
 | 
			
		||||
		Clusters:    make(map[string]Cluster),
 | 
			
		||||
		AuthInfos:   make(map[string]AuthInfo),
 | 
			
		||||
		Contexts:    make(map[string]Context),
 | 
			
		||||
		Extensions:  make(map[string]runtime.EmbeddedObject),
 | 
			
		||||
		Clusters:    make(map[string]*Cluster),
 | 
			
		||||
		AuthInfos:   make(map[string]*AuthInfo),
 | 
			
		||||
		Contexts:    make(map[string]*Context),
 | 
			
		||||
		Extensions:  make(map[string]*runtime.EmbeddedObject),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
 | 
			
		||||
func NewContext() *Context {
 | 
			
		||||
	return &Context{Extensions: make(map[string]runtime.EmbeddedObject)}
 | 
			
		||||
	return &Context{Extensions: make(map[string]*runtime.EmbeddedObject)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
 | 
			
		||||
func NewCluster() *Cluster {
 | 
			
		||||
	return &Cluster{Extensions: make(map[string]runtime.EmbeddedObject)}
 | 
			
		||||
	return &Cluster{Extensions: make(map[string]*runtime.EmbeddedObject)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
 | 
			
		||||
func NewAuthInfo() *AuthInfo {
 | 
			
		||||
	return &AuthInfo{Extensions: make(map[string]runtime.EmbeddedObject)}
 | 
			
		||||
	return &AuthInfo{Extensions: make(map[string]*runtime.EmbeddedObject)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConfig is a convenience function that returns a new Config object with non-nil maps
 | 
			
		||||
func NewPreferences() *Preferences {
 | 
			
		||||
	return &Preferences{Extensions: make(map[string]runtime.EmbeddedObject)}
 | 
			
		||||
	return &Preferences{Extensions: make(map[string]*runtime.EmbeddedObject)}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,35 +42,35 @@ func ExampleEmptyConfig() {
 | 
			
		||||
func ExampleOfOptionsConfig() {
 | 
			
		||||
	defaultConfig := NewConfig()
 | 
			
		||||
	defaultConfig.Preferences.Colors = true
 | 
			
		||||
	defaultConfig.Clusters["alfa"] = Cluster{
 | 
			
		||||
	defaultConfig.Clusters["alfa"] = &Cluster{
 | 
			
		||||
		Server:                "https://alfa.org:8080",
 | 
			
		||||
		APIVersion:            "v1beta2",
 | 
			
		||||
		InsecureSkipTLSVerify: true,
 | 
			
		||||
		CertificateAuthority:  "path/to/my/cert-ca-filename",
 | 
			
		||||
	}
 | 
			
		||||
	defaultConfig.Clusters["bravo"] = Cluster{
 | 
			
		||||
	defaultConfig.Clusters["bravo"] = &Cluster{
 | 
			
		||||
		Server:                "https://bravo.org:8080",
 | 
			
		||||
		APIVersion:            "v1beta1",
 | 
			
		||||
		InsecureSkipTLSVerify: false,
 | 
			
		||||
	}
 | 
			
		||||
	defaultConfig.AuthInfos["white-mage-via-cert"] = AuthInfo{
 | 
			
		||||
	defaultConfig.AuthInfos["white-mage-via-cert"] = &AuthInfo{
 | 
			
		||||
		ClientCertificate: "path/to/my/client-cert-filename",
 | 
			
		||||
		ClientKey:         "path/to/my/client-key-filename",
 | 
			
		||||
	}
 | 
			
		||||
	defaultConfig.AuthInfos["red-mage-via-token"] = AuthInfo{
 | 
			
		||||
	defaultConfig.AuthInfos["red-mage-via-token"] = &AuthInfo{
 | 
			
		||||
		Token: "my-secret-token",
 | 
			
		||||
	}
 | 
			
		||||
	defaultConfig.Contexts["bravo-as-black-mage"] = Context{
 | 
			
		||||
	defaultConfig.Contexts["bravo-as-black-mage"] = &Context{
 | 
			
		||||
		Cluster:   "bravo",
 | 
			
		||||
		AuthInfo:  "black-mage-via-file",
 | 
			
		||||
		Namespace: "yankee",
 | 
			
		||||
	}
 | 
			
		||||
	defaultConfig.Contexts["alfa-as-black-mage"] = Context{
 | 
			
		||||
	defaultConfig.Contexts["alfa-as-black-mage"] = &Context{
 | 
			
		||||
		Cluster:   "alfa",
 | 
			
		||||
		AuthInfo:  "black-mage-via-file",
 | 
			
		||||
		Namespace: "zulu",
 | 
			
		||||
	}
 | 
			
		||||
	defaultConfig.Contexts["alfa-as-white-mage"] = Context{
 | 
			
		||||
	defaultConfig.Contexts["alfa-as-white-mage"] = &Context{
 | 
			
		||||
		Cluster:  "alfa",
 | 
			
		||||
		AuthInfo: "white-mage-via-cert",
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,19 +57,19 @@ func init() {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			out.Clusters = make(map[string]api.Cluster)
 | 
			
		||||
			out.Clusters = make(map[string]*api.Cluster)
 | 
			
		||||
			if err := s.Convert(&in.Clusters, &out.Clusters, 0); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			out.AuthInfos = make(map[string]api.AuthInfo)
 | 
			
		||||
			out.AuthInfos = make(map[string]*api.AuthInfo)
 | 
			
		||||
			if err := s.Convert(&in.AuthInfos, &out.AuthInfos, 0); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			out.Contexts = make(map[string]api.Context)
 | 
			
		||||
			out.Contexts = make(map[string]*api.Context)
 | 
			
		||||
			if err := s.Convert(&in.Contexts, &out.Contexts, 0); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			out.Extensions = make(map[string]runtime.EmbeddedObject)
 | 
			
		||||
			out.Extensions = make(map[string]*runtime.EmbeddedObject)
 | 
			
		||||
			if err := s.Convert(&in.Extensions, &out.Extensions, 0); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
@@ -99,18 +99,18 @@ func init() {
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *[]NamedCluster, out *map[string]api.Cluster, s conversion.Scope) error {
 | 
			
		||||
		func(in *[]NamedCluster, out *map[string]*api.Cluster, s conversion.Scope) error {
 | 
			
		||||
			for _, curr := range *in {
 | 
			
		||||
				newCluster := api.NewCluster()
 | 
			
		||||
				if err := s.Convert(&curr.Cluster, newCluster, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				(*out)[curr.Name] = *newCluster
 | 
			
		||||
				(*out)[curr.Name] = newCluster
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *map[string]api.Cluster, out *[]NamedCluster, s conversion.Scope) error {
 | 
			
		||||
		func(in *map[string]*api.Cluster, out *[]NamedCluster, s conversion.Scope) error {
 | 
			
		||||
			allKeys := make([]string, 0, len(*in))
 | 
			
		||||
			for key := range *in {
 | 
			
		||||
				allKeys = append(allKeys, key)
 | 
			
		||||
@@ -120,7 +120,7 @@ func init() {
 | 
			
		||||
			for _, key := range allKeys {
 | 
			
		||||
				newCluster := (*in)[key]
 | 
			
		||||
				oldCluster := &Cluster{}
 | 
			
		||||
				if err := s.Convert(&newCluster, oldCluster, 0); err != nil {
 | 
			
		||||
				if err := s.Convert(newCluster, oldCluster, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -130,18 +130,18 @@ func init() {
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *[]NamedAuthInfo, out *map[string]api.AuthInfo, s conversion.Scope) error {
 | 
			
		||||
		func(in *[]NamedAuthInfo, out *map[string]*api.AuthInfo, s conversion.Scope) error {
 | 
			
		||||
			for _, curr := range *in {
 | 
			
		||||
				newAuthInfo := api.NewAuthInfo()
 | 
			
		||||
				if err := s.Convert(&curr.AuthInfo, newAuthInfo, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				(*out)[curr.Name] = *newAuthInfo
 | 
			
		||||
				(*out)[curr.Name] = newAuthInfo
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *map[string]api.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error {
 | 
			
		||||
		func(in *map[string]*api.AuthInfo, out *[]NamedAuthInfo, s conversion.Scope) error {
 | 
			
		||||
			allKeys := make([]string, 0, len(*in))
 | 
			
		||||
			for key := range *in {
 | 
			
		||||
				allKeys = append(allKeys, key)
 | 
			
		||||
@@ -151,7 +151,7 @@ func init() {
 | 
			
		||||
			for _, key := range allKeys {
 | 
			
		||||
				newAuthInfo := (*in)[key]
 | 
			
		||||
				oldAuthInfo := &AuthInfo{}
 | 
			
		||||
				if err := s.Convert(&newAuthInfo, oldAuthInfo, 0); err != nil {
 | 
			
		||||
				if err := s.Convert(newAuthInfo, oldAuthInfo, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -161,18 +161,18 @@ func init() {
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *[]NamedContext, out *map[string]api.Context, s conversion.Scope) error {
 | 
			
		||||
		func(in *[]NamedContext, out *map[string]*api.Context, s conversion.Scope) error {
 | 
			
		||||
			for _, curr := range *in {
 | 
			
		||||
				newContext := api.NewContext()
 | 
			
		||||
				if err := s.Convert(&curr.Context, newContext, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				(*out)[curr.Name] = *newContext
 | 
			
		||||
				(*out)[curr.Name] = newContext
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *map[string]api.Context, out *[]NamedContext, s conversion.Scope) error {
 | 
			
		||||
		func(in *map[string]*api.Context, out *[]NamedContext, s conversion.Scope) error {
 | 
			
		||||
			allKeys := make([]string, 0, len(*in))
 | 
			
		||||
			for key := range *in {
 | 
			
		||||
				allKeys = append(allKeys, key)
 | 
			
		||||
@@ -182,7 +182,7 @@ func init() {
 | 
			
		||||
			for _, key := range allKeys {
 | 
			
		||||
				newContext := (*in)[key]
 | 
			
		||||
				oldContext := &Context{}
 | 
			
		||||
				if err := s.Convert(&newContext, oldContext, 0); err != nil {
 | 
			
		||||
				if err := s.Convert(newContext, oldContext, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
@@ -192,18 +192,18 @@ func init() {
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *[]NamedExtension, out *map[string]runtime.EmbeddedObject, s conversion.Scope) error {
 | 
			
		||||
		func(in *[]NamedExtension, out *map[string]*runtime.EmbeddedObject, s conversion.Scope) error {
 | 
			
		||||
			for _, curr := range *in {
 | 
			
		||||
				newExtension := &runtime.EmbeddedObject{}
 | 
			
		||||
				if err := s.Convert(&curr.Extension, newExtension, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				(*out)[curr.Name] = *newExtension
 | 
			
		||||
				(*out)[curr.Name] = newExtension
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
		func(in *map[string]runtime.EmbeddedObject, out *[]NamedExtension, s conversion.Scope) error {
 | 
			
		||||
		func(in *map[string]*runtime.EmbeddedObject, out *[]NamedExtension, s conversion.Scope) error {
 | 
			
		||||
			allKeys := make([]string, 0, len(*in))
 | 
			
		||||
			for key := range *in {
 | 
			
		||||
				allKeys = append(allKeys, key)
 | 
			
		||||
@@ -213,7 +213,7 @@ func init() {
 | 
			
		||||
			for _, key := range allKeys {
 | 
			
		||||
				newExtension := (*in)[key]
 | 
			
		||||
				oldExtension := &runtime.RawExtension{}
 | 
			
		||||
				if err := s.Convert(&newExtension, oldExtension, 0); err != nil {
 | 
			
		||||
				if err := s.Convert(newExtension, oldExtension, 0); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,14 +32,14 @@ func createValidTestConfig() *clientcmdapi.Config {
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["clean"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["clean"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:     server,
 | 
			
		||||
		APIVersion: latest.Version,
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Token: token,
 | 
			
		||||
	}
 | 
			
		||||
	config.Contexts["clean"] = clientcmdapi.Context{
 | 
			
		||||
	config.Contexts["clean"] = &clientcmdapi.Context{
 | 
			
		||||
		Cluster:  "clean",
 | 
			
		||||
		AuthInfo: "clean",
 | 
			
		||||
	}
 | 
			
		||||
@@ -87,16 +87,16 @@ func TestCertificateData(t *testing.T) {
 | 
			
		||||
	keyData := []byte("key-data")
 | 
			
		||||
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["clean"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["clean"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:                   "https://localhost:8443",
 | 
			
		||||
		APIVersion:               latest.Version,
 | 
			
		||||
		CertificateAuthorityData: caData,
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		ClientCertificateData: certData,
 | 
			
		||||
		ClientKeyData:         keyData,
 | 
			
		||||
	}
 | 
			
		||||
	config.Contexts["clean"] = clientcmdapi.Context{
 | 
			
		||||
	config.Contexts["clean"] = &clientcmdapi.Context{
 | 
			
		||||
		Cluster:  "clean",
 | 
			
		||||
		AuthInfo: "clean",
 | 
			
		||||
	}
 | 
			
		||||
@@ -120,15 +120,15 @@ func TestBasicAuthData(t *testing.T) {
 | 
			
		||||
	password := "mypass"
 | 
			
		||||
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["clean"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["clean"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:     "https://localhost:8443",
 | 
			
		||||
		APIVersion: latest.Version,
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Username: username,
 | 
			
		||||
		Password: password,
 | 
			
		||||
	}
 | 
			
		||||
	config.Contexts["clean"] = clientcmdapi.Context{
 | 
			
		||||
	config.Contexts["clean"] = &clientcmdapi.Context{
 | 
			
		||||
		Cluster:  "clean",
 | 
			
		||||
		AuthInfo: "clean",
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/ghodss/yaml"
 | 
			
		||||
	"github.com/imdario/mergo"
 | 
			
		||||
@@ -120,11 +121,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
 | 
			
		||||
		if err := mergeConfigWithFile(mapConfig, file); err != nil {
 | 
			
		||||
			errlist = append(errlist, err)
 | 
			
		||||
		}
 | 
			
		||||
		if rules.ResolvePaths() {
 | 
			
		||||
			if err := ResolveLocalPaths(file, mapConfig); err != nil {
 | 
			
		||||
				errlist = append(errlist, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// merge all of the struct values in the reverse order so that priority is given correctly
 | 
			
		||||
@@ -133,9 +129,6 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
 | 
			
		||||
	for i := len(kubeConfigFiles) - 1; i >= 0; i-- {
 | 
			
		||||
		file := kubeConfigFiles[i]
 | 
			
		||||
		mergeConfigWithFile(nonMapConfig, file)
 | 
			
		||||
		if rules.ResolvePaths() {
 | 
			
		||||
			ResolveLocalPaths(file, nonMapConfig)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// since values are overwritten, but maps values are not, we can merge the non-map config on top of the map config and
 | 
			
		||||
@@ -144,6 +137,12 @@ func (rules *ClientConfigLoadingRules) Load() (*clientcmdapi.Config, error) {
 | 
			
		||||
	mergo.Merge(config, mapConfig)
 | 
			
		||||
	mergo.Merge(config, nonMapConfig)
 | 
			
		||||
 | 
			
		||||
	if rules.ResolvePaths() {
 | 
			
		||||
		if err := ResolveLocalPaths(config); err != nil {
 | 
			
		||||
			errlist = append(errlist, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config, errors.NewAggregate(errlist)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -213,49 +212,6 @@ func mergeConfigWithFile(startingConfig *clientcmdapi.Config, filename string) e
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResolveLocalPaths resolves all relative paths in the config object with respect to the parent directory of the filename
 | 
			
		||||
// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without
 | 
			
		||||
// modification of its contents.
 | 
			
		||||
func ResolveLocalPaths(filename string, config *clientcmdapi.Config) error {
 | 
			
		||||
	if len(filename) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	configDir, err := filepath.Abs(filepath.Dir(filename))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Could not determine the absolute path of config file %s: %v", filename, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resolvedClusters := make(map[string]clientcmdapi.Cluster)
 | 
			
		||||
	for key, cluster := range config.Clusters {
 | 
			
		||||
		cluster.CertificateAuthority = resolveLocalPath(configDir, cluster.CertificateAuthority)
 | 
			
		||||
		resolvedClusters[key] = cluster
 | 
			
		||||
	}
 | 
			
		||||
	config.Clusters = resolvedClusters
 | 
			
		||||
 | 
			
		||||
	resolvedAuthInfos := make(map[string]clientcmdapi.AuthInfo)
 | 
			
		||||
	for key, authInfo := range config.AuthInfos {
 | 
			
		||||
		authInfo.ClientCertificate = resolveLocalPath(configDir, authInfo.ClientCertificate)
 | 
			
		||||
		authInfo.ClientKey = resolveLocalPath(configDir, authInfo.ClientKey)
 | 
			
		||||
		resolvedAuthInfos[key] = authInfo
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos = resolvedAuthInfos
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// resolveLocalPath makes the path absolute with respect to the startingDir
 | 
			
		||||
func resolveLocalPath(startingDir, path string) string {
 | 
			
		||||
	if len(path) == 0 {
 | 
			
		||||
		return path
 | 
			
		||||
	}
 | 
			
		||||
	if filepath.IsAbs(path) {
 | 
			
		||||
		return path
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return filepath.Join(startingDir, path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadFromFile takes a filename and deserializes the contents into Config object
 | 
			
		||||
func LoadFromFile(filename string) (*clientcmdapi.Config, error) {
 | 
			
		||||
	kubeconfigBytes, err := ioutil.ReadFile(filename)
 | 
			
		||||
@@ -335,3 +291,159 @@ func Write(config clientcmdapi.Config) ([]byte, error) {
 | 
			
		||||
func (rules ClientConfigLoadingRules) ResolvePaths() bool {
 | 
			
		||||
	return !rules.DoNotResolvePaths
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResolveLocalPaths resolves all relative paths in the config object with respect to the stanza's LocationOfOrigin
 | 
			
		||||
// this cannot be done directly inside of LoadFromFile because doing so there would make it impossible to load a file without
 | 
			
		||||
// modification of its contents.
 | 
			
		||||
func ResolveLocalPaths(config *clientcmdapi.Config) error {
 | 
			
		||||
	for _, cluster := range config.Clusters {
 | 
			
		||||
		if len(cluster.LocationOfOrigin) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, authInfo := range config.AuthInfos {
 | 
			
		||||
		if len(authInfo.LocationOfOrigin) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RelativizeClusterLocalPaths first absolutizes the paths by calling ResolveLocalPaths.  This assumes that any NEW path is already
 | 
			
		||||
// absolute, but any existing path will be resolved relative to LocationOfOrigin
 | 
			
		||||
func RelativizeClusterLocalPaths(cluster *clientcmdapi.Cluster) error {
 | 
			
		||||
	if len(cluster.LocationOfOrigin) == 0 {
 | 
			
		||||
		return fmt.Errorf("no location of origin for %s", cluster.Server)
 | 
			
		||||
	}
 | 
			
		||||
	base, err := filepath.Abs(filepath.Dir(cluster.LocationOfOrigin))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("could not determine the absolute path of config file %s: %v", cluster.LocationOfOrigin, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ResolvePaths(GetClusterFileReferences(cluster), base); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := RelativizePathWithNoBacksteps(GetClusterFileReferences(cluster), base); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RelativizeAuthInfoLocalPaths first absolutizes the paths by calling ResolveLocalPaths.  This assumes that any NEW path is already
 | 
			
		||||
// absolute, but any existing path will be resolved relative to LocationOfOrigin
 | 
			
		||||
func RelativizeAuthInfoLocalPaths(authInfo *clientcmdapi.AuthInfo) error {
 | 
			
		||||
	if len(authInfo.LocationOfOrigin) == 0 {
 | 
			
		||||
		return fmt.Errorf("no location of origin for %v", authInfo)
 | 
			
		||||
	}
 | 
			
		||||
	base, err := filepath.Abs(filepath.Dir(authInfo.LocationOfOrigin))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("could not determine the absolute path of config file %s: %v", authInfo.LocationOfOrigin, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ResolvePaths(GetAuthInfoFileReferences(authInfo), base); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := RelativizePathWithNoBacksteps(GetAuthInfoFileReferences(authInfo), base); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RelativizeConfigPaths(config *clientcmdapi.Config, base string) error {
 | 
			
		||||
	return RelativizePathWithNoBacksteps(GetConfigFileReferences(config), base)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ResolveConfigPaths(config *clientcmdapi.Config, base string) error {
 | 
			
		||||
	return ResolvePaths(GetConfigFileReferences(config), base)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetConfigFileReferences(config *clientcmdapi.Config) []*string {
 | 
			
		||||
	refs := []*string{}
 | 
			
		||||
 | 
			
		||||
	for _, cluster := range config.Clusters {
 | 
			
		||||
		refs = append(refs, GetClusterFileReferences(cluster)...)
 | 
			
		||||
	}
 | 
			
		||||
	for _, authInfo := range config.AuthInfos {
 | 
			
		||||
		refs = append(refs, GetAuthInfoFileReferences(authInfo)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return refs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetClusterFileReferences(cluster *clientcmdapi.Cluster) []*string {
 | 
			
		||||
	return []*string{&cluster.CertificateAuthority}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAuthInfoFileReferences(authInfo *clientcmdapi.AuthInfo) []*string {
 | 
			
		||||
	return []*string{&authInfo.ClientCertificate, &authInfo.ClientKey}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResolvePaths updates the given refs to be absolute paths, relative to the given base directory
 | 
			
		||||
func ResolvePaths(refs []*string, base string) error {
 | 
			
		||||
	for _, ref := range refs {
 | 
			
		||||
		// Don't resolve empty paths
 | 
			
		||||
		if len(*ref) > 0 {
 | 
			
		||||
			// Don't resolve absolute paths
 | 
			
		||||
			if !filepath.IsAbs(*ref) {
 | 
			
		||||
				*ref = filepath.Join(base, *ref)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RelativizePathWithNoBacksteps updates the given refs to be relative paths, relative to the given base directory as long as they do not require backsteps.
 | 
			
		||||
// Any path requiring a backstep is left as-is as long it is absolute.  Any non-absolute path that can't be relativized produces an error
 | 
			
		||||
func RelativizePathWithNoBacksteps(refs []*string, base string) error {
 | 
			
		||||
	for _, ref := range refs {
 | 
			
		||||
		// Don't relativize empty paths
 | 
			
		||||
		if len(*ref) > 0 {
 | 
			
		||||
			rel, err := MakeRelative(*ref, base)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// if we have a backstep, don't mess with the path
 | 
			
		||||
			if strings.HasPrefix(rel, "../") {
 | 
			
		||||
				if filepath.IsAbs(*ref) {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return fmt.Errorf("%v requires backsteps and is not absolute", *ref)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			*ref = rel
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func MakeRelative(path, base string) (string, error) {
 | 
			
		||||
	if len(path) > 0 {
 | 
			
		||||
		rel, err := filepath.Rel(base, path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return path, err
 | 
			
		||||
		}
 | 
			
		||||
		return rel, nil
 | 
			
		||||
	}
 | 
			
		||||
	return path, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,43 +34,43 @@ import (
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	testConfigAlfa = clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"red-user": {Token: "red-token"}},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"cow-cluster": {Server: "http://cow.org:8080"}},
 | 
			
		||||
		Contexts: map[string]clientcmdapi.Context{
 | 
			
		||||
		Contexts: map[string]*clientcmdapi.Context{
 | 
			
		||||
			"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster", Namespace: "hammer-ns"}},
 | 
			
		||||
	}
 | 
			
		||||
	testConfigBravo = clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"black-user": {Token: "black-token"}},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"pig-cluster": {Server: "http://pig.org:8080"}},
 | 
			
		||||
		Contexts: map[string]clientcmdapi.Context{
 | 
			
		||||
		Contexts: map[string]*clientcmdapi.Context{
 | 
			
		||||
			"queen-anne-context": {AuthInfo: "black-user", Cluster: "pig-cluster", Namespace: "saw-ns"}},
 | 
			
		||||
	}
 | 
			
		||||
	testConfigCharlie = clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"green-user": {Token: "green-token"}},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"horse-cluster": {Server: "http://horse.org:8080"}},
 | 
			
		||||
		Contexts: map[string]clientcmdapi.Context{
 | 
			
		||||
		Contexts: map[string]*clientcmdapi.Context{
 | 
			
		||||
			"shaker-context": {AuthInfo: "green-user", Cluster: "horse-cluster", Namespace: "chisel-ns"}},
 | 
			
		||||
	}
 | 
			
		||||
	testConfigDelta = clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"blue-user": {Token: "blue-token"}},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"chicken-cluster": {Server: "http://chicken.org:8080"}},
 | 
			
		||||
		Contexts: map[string]clientcmdapi.Context{
 | 
			
		||||
		Contexts: map[string]*clientcmdapi.Context{
 | 
			
		||||
			"gothic-context": {AuthInfo: "blue-user", Cluster: "chicken-cluster", Namespace: "plane-ns"}},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	testConfigConflictAlfa = clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"red-user":    {Token: "a-different-red-token"},
 | 
			
		||||
			"yellow-user": {Token: "yellow-token"}},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"cow-cluster":    {Server: "http://a-different-cow.org:8080", InsecureSkipTLSVerify: true},
 | 
			
		||||
			"donkey-cluster": {Server: "http://donkey.org:8080", InsecureSkipTLSVerify: true}},
 | 
			
		||||
		CurrentContext: "federal-context",
 | 
			
		||||
@@ -176,21 +176,21 @@ func TestConflictingCurrentContext(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestResolveRelativePaths(t *testing.T) {
 | 
			
		||||
	pathResolutionConfig1 := clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"relative-user-1": {ClientCertificate: "relative/client/cert", ClientKey: "../relative/client/key"},
 | 
			
		||||
			"absolute-user-1": {ClientCertificate: "/absolute/client/cert", ClientKey: "/absolute/client/key"},
 | 
			
		||||
		},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"relative-server-1": {CertificateAuthority: "../relative/ca"},
 | 
			
		||||
			"absolute-server-1": {CertificateAuthority: "/absolute/ca"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	pathResolutionConfig2 := clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"relative-user-2": {ClientCertificate: "relative/client/cert2", ClientKey: "../relative/client/key2"},
 | 
			
		||||
			"absolute-user-2": {ClientCertificate: "/absolute/client/cert2", ClientKey: "/absolute/client/key2"},
 | 
			
		||||
		},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"relative-server-2": {CertificateAuthority: "../relative/ca2"},
 | 
			
		||||
			"absolute-server-2": {CertificateAuthority: "/absolute/ca2"},
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
@@ -95,15 +95,15 @@ func Validate(config clientcmdapi.Config) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for contextName, context := range config.Contexts {
 | 
			
		||||
		validationErrors = append(validationErrors, validateContext(contextName, context, config)...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for authInfoName, authInfo := range config.AuthInfos {
 | 
			
		||||
		validationErrors = append(validationErrors, validateAuthInfo(authInfoName, authInfo)...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateAuthInfo(authInfoName, *authInfo)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for clusterName, clusterInfo := range config.Clusters {
 | 
			
		||||
		validationErrors = append(validationErrors, validateClusterInfo(clusterName, clusterInfo)...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateClusterInfo(clusterName, *clusterInfo)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newErrConfigurationInvalid(validationErrors)
 | 
			
		||||
@@ -131,9 +131,9 @@ func ConfirmUsable(config clientcmdapi.Config, passedContextName string) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if exists {
 | 
			
		||||
		validationErrors = append(validationErrors, validateContext(contextName, context, config)...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, config.AuthInfos[context.AuthInfo])...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, config.Clusters[context.Cluster])...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateContext(contextName, *context, config)...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateAuthInfo(context.AuthInfo, *config.AuthInfos[context.AuthInfo])...)
 | 
			
		||||
		validationErrors = append(validationErrors, validateClusterInfo(context.Cluster, *config.Clusters[context.Cluster])...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return newErrConfigurationInvalid(validationErrors)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,25 +28,25 @@ import (
 | 
			
		||||
 | 
			
		||||
func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["missing ca"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["missing ca"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:               "anything",
 | 
			
		||||
		CertificateAuthority: "missing",
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos["error"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Username: "anything",
 | 
			
		||||
		Token:    "here",
 | 
			
		||||
	}
 | 
			
		||||
	config.Contexts["dirty"] = clientcmdapi.Context{
 | 
			
		||||
	config.Contexts["dirty"] = &clientcmdapi.Context{
 | 
			
		||||
		Cluster:  "missing ca",
 | 
			
		||||
		AuthInfo: "error",
 | 
			
		||||
	}
 | 
			
		||||
	config.Clusters["clean"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["clean"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server: "anything",
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Token: "here",
 | 
			
		||||
	}
 | 
			
		||||
	config.Contexts["clean"] = clientcmdapi.Context{
 | 
			
		||||
	config.Contexts["clean"] = &clientcmdapi.Context{
 | 
			
		||||
		Cluster:  "clean",
 | 
			
		||||
		AuthInfo: "clean",
 | 
			
		||||
	}
 | 
			
		||||
@@ -64,15 +64,15 @@ func TestConfirmUsableBadInfoButOkConfig(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
func TestConfirmUsableBadInfoConfig(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["missing ca"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["missing ca"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:               "anything",
 | 
			
		||||
		CertificateAuthority: "missing",
 | 
			
		||||
	}
 | 
			
		||||
	config.AuthInfos["error"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Username: "anything",
 | 
			
		||||
		Token:    "here",
 | 
			
		||||
	}
 | 
			
		||||
	config.Contexts["first"] = clientcmdapi.Context{
 | 
			
		||||
	config.Contexts["first"] = &clientcmdapi.Context{
 | 
			
		||||
		Cluster:  "missing ca",
 | 
			
		||||
		AuthInfo: "error",
 | 
			
		||||
	}
 | 
			
		||||
@@ -150,7 +150,7 @@ func TestIsConfigurationInvalid(t *testing.T) {
 | 
			
		||||
func TestValidateMissingReferencesConfig(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.CurrentContext = "anything"
 | 
			
		||||
	config.Contexts["anything"] = clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
 | 
			
		||||
	config.Contexts["anything"] = &clientcmdapi.Context{Cluster: "missing", AuthInfo: "missing"}
 | 
			
		||||
	test := configValidationTest{
 | 
			
		||||
		config:                 config,
 | 
			
		||||
		expectedErrorSubstring: []string{"user \"missing\" was not found for context \"anything\"", "cluster \"missing\" was not found for context \"anything\""},
 | 
			
		||||
@@ -162,7 +162,7 @@ func TestValidateMissingReferencesConfig(t *testing.T) {
 | 
			
		||||
func TestValidateEmptyContext(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.CurrentContext = "anything"
 | 
			
		||||
	config.Contexts["anything"] = clientcmdapi.Context{}
 | 
			
		||||
	config.Contexts["anything"] = &clientcmdapi.Context{}
 | 
			
		||||
	test := configValidationTest{
 | 
			
		||||
		config:                 config,
 | 
			
		||||
		expectedErrorSubstring: []string{"user was not specified for context \"anything\"", "cluster was not specified for context \"anything\""},
 | 
			
		||||
@@ -174,7 +174,7 @@ func TestValidateEmptyContext(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestValidateEmptyClusterInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["empty"] = clientcmdapi.Cluster{}
 | 
			
		||||
	config.Clusters["empty"] = &clientcmdapi.Cluster{}
 | 
			
		||||
	test := configValidationTest{
 | 
			
		||||
		config:                 config,
 | 
			
		||||
		expectedErrorSubstring: []string{"no server found for"},
 | 
			
		||||
@@ -185,7 +185,7 @@ func TestValidateEmptyClusterInfo(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
func TestValidateMissingCAFileClusterInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["missing ca"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["missing ca"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:               "anything",
 | 
			
		||||
		CertificateAuthority: "missing",
 | 
			
		||||
	}
 | 
			
		||||
@@ -199,7 +199,7 @@ func TestValidateMissingCAFileClusterInfo(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
func TestValidateCleanClusterInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["clean"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["clean"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server: "anything",
 | 
			
		||||
	}
 | 
			
		||||
	test := configValidationTest{
 | 
			
		||||
@@ -214,7 +214,7 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) {
 | 
			
		||||
	defer os.Remove(tempFile.Name())
 | 
			
		||||
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.Clusters["clean"] = clientcmdapi.Cluster{
 | 
			
		||||
	config.Clusters["clean"] = &clientcmdapi.Cluster{
 | 
			
		||||
		Server:               "anything",
 | 
			
		||||
		CertificateAuthority: tempFile.Name(),
 | 
			
		||||
	}
 | 
			
		||||
@@ -228,7 +228,7 @@ func TestValidateCleanWithCAClusterInfo(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestValidateEmptyAuthInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.AuthInfos["error"] = clientcmdapi.AuthInfo{}
 | 
			
		||||
	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{}
 | 
			
		||||
	test := configValidationTest{
 | 
			
		||||
		config: config,
 | 
			
		||||
	}
 | 
			
		||||
@@ -238,7 +238,7 @@ func TestValidateEmptyAuthInfo(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
func TestValidateCertFilesNotFoundAuthInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.AuthInfos["error"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		ClientCertificate: "missing",
 | 
			
		||||
		ClientKey:         "missing",
 | 
			
		||||
	}
 | 
			
		||||
@@ -255,7 +255,7 @@ func TestValidateCertDataOverridesFiles(t *testing.T) {
 | 
			
		||||
	defer os.Remove(tempFile.Name())
 | 
			
		||||
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		ClientCertificate:     tempFile.Name(),
 | 
			
		||||
		ClientCertificateData: []byte("certdata"),
 | 
			
		||||
		ClientKey:             tempFile.Name(),
 | 
			
		||||
@@ -274,7 +274,7 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
 | 
			
		||||
	defer os.Remove(tempFile.Name())
 | 
			
		||||
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		ClientCertificate: tempFile.Name(),
 | 
			
		||||
		ClientKey:         tempFile.Name(),
 | 
			
		||||
	}
 | 
			
		||||
@@ -287,7 +287,7 @@ func TestValidateCleanCertFilesAuthInfo(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
func TestValidateCleanTokenAuthInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.AuthInfos["clean"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["clean"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Token: "any-value",
 | 
			
		||||
	}
 | 
			
		||||
	test := configValidationTest{
 | 
			
		||||
@@ -300,7 +300,7 @@ func TestValidateCleanTokenAuthInfo(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestValidateMultipleMethodsAuthInfo(t *testing.T) {
 | 
			
		||||
	config := clientcmdapi.NewConfig()
 | 
			
		||||
	config.AuthInfos["error"] = clientcmdapi.AuthInfo{
 | 
			
		||||
	config.AuthInfos["error"] = &clientcmdapi.AuthInfo{
 | 
			
		||||
		Token:    "token",
 | 
			
		||||
		Username: "username",
 | 
			
		||||
	}
 | 
			
		||||
@@ -319,7 +319,7 @@ type configValidationTest struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c configValidationTest) testContext(contextName string, t *testing.T) {
 | 
			
		||||
	errs := validateContext(contextName, c.config.Contexts[contextName], *c.config)
 | 
			
		||||
	errs := validateContext(contextName, *c.config.Contexts[contextName], *c.config)
 | 
			
		||||
 | 
			
		||||
	if len(c.expectedErrorSubstring) != 0 {
 | 
			
		||||
		if len(errs) == 0 {
 | 
			
		||||
@@ -379,7 +379,7 @@ func (c configValidationTest) testConfig(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
 | 
			
		||||
	errs := validateClusterInfo(clusterName, c.config.Clusters[clusterName])
 | 
			
		||||
	errs := validateClusterInfo(clusterName, *c.config.Clusters[clusterName])
 | 
			
		||||
 | 
			
		||||
	if len(c.expectedErrorSubstring) != 0 {
 | 
			
		||||
		if len(errs) == 0 {
 | 
			
		||||
@@ -399,7 +399,7 @@ func (c configValidationTest) testCluster(clusterName string, t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c configValidationTest) testAuthInfo(authInfoName string, t *testing.T) {
 | 
			
		||||
	errs := validateAuthInfo(authInfoName, c.config.AuthInfos[authInfoName])
 | 
			
		||||
	errs := validateAuthInfo(authInfoName, *c.config.AuthInfos[authInfoName])
 | 
			
		||||
 | 
			
		||||
	if len(c.expectedErrorSubstring) != 0 {
 | 
			
		||||
		if len(errs) == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -51,8 +51,8 @@ func (c *FakeEndpoints) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeEndpoints) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-endpoints", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-endpoints", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeEndpoints) Update(endpoints *api.Endpoints) (*api.Endpoints, error) {
 | 
			
		||||
 
 | 
			
		||||
@@ -56,8 +56,8 @@ func (c *FakeEvents) Get(id string) (*api.Event, error) {
 | 
			
		||||
 | 
			
		||||
// Watch starts watching for events matching the given selectors.
 | 
			
		||||
func (c *FakeEvents) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-events", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-events", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Search returns a list of events matching the specified object.
 | 
			
		||||
@@ -72,6 +72,6 @@ func (c *FakeEvents) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeEvents) GetFieldSelector(involvedObjectName, involvedObjectNamespace, involvedObjectKind, involvedObjectUID *string) fields.Selector {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "get-field-selector"})
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "get-field-selector"}, nil)
 | 
			
		||||
	return fields.Everything()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,6 @@ func (c *FakeLimitRanges) Update(limitRange *api.LimitRange) (*api.LimitRange, e
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeLimitRanges) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-limitRange", Value: resourceVersion})
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-limitRange", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,8 +45,8 @@ func (c *FakeNamespaces) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeNamespaces) Create(namespace *api.Namespace) (*api.Namespace, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "create-namespace"})
 | 
			
		||||
	return &api.Namespace{}, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "create-namespace"}, nil)
 | 
			
		||||
	return &api.Namespace{}, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeNamespaces) Update(namespace *api.Namespace) (*api.Namespace, error) {
 | 
			
		||||
@@ -55,7 +55,7 @@ func (c *FakeNamespaces) Update(namespace *api.Namespace) (*api.Namespace, error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeNamespaces) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-namespaces", Value: resourceVersion})
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-namespaces", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,6 @@ func (c *FakeNodes) UpdateStatus(minion *api.Node) (*api.Node, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeNodes) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-nodes", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-nodes", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,6 @@ func (c *FakePersistentVolumeClaims) UpdateStatus(claim *api.PersistentVolumeCla
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakePersistentVolumeClaims) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-persistentVolumeClaims", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-persistentVolumeClaims", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,6 +59,6 @@ func (c *FakePersistentVolumes) UpdateStatus(pv *api.PersistentVolume) (*api.Per
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakePersistentVolumes) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-persistentVolumes", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-persistentVolumes", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,6 @@ func (c *FakePodTemplates) Update(pod *api.PodTemplate) (*api.PodTemplate, error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakePodTemplates) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-podTemplates", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-podTemplates", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,12 +56,12 @@ func (c *FakePods) Update(pod *api.Pod) (*api.Pod, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakePods) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-pods", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-pods", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakePods) Bind(bind *api.Binding) error {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "bind-pod", Value: bind.Name})
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "bind-pod", Value: bind.Name}, nil)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,6 @@ func (c *FakeReplicationControllers) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeReplicationControllers) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: WatchControllerAction, Value: resourceVersion})
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: WatchControllerAction, Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,6 @@ func (c *FakeResourceQuotas) UpdateStatus(resourceQuota *api.ResourceQuota) (*ap
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeResourceQuotas) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-resourceQuota", Value: resourceVersion})
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-resourceQuota", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,6 @@ func (c *FakeSecrets) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeSecrets) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-secrets", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-secrets", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,6 @@ func (c *FakeServiceAccounts) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeServiceAccounts) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-serviceAccounts", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-serviceAccounts", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,6 @@ func (c *FakeServices) Delete(name string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *FakeServices) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, FakeAction{Action: "watch-services", Value: resourceVersion})
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err
 | 
			
		||||
	c.Fake.Invokes(FakeAction{Action: "watch-services", Value: resourceVersion}, nil)
 | 
			
		||||
	return c.Fake.Watch, c.Fake.Err()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,8 @@ limitations under the License.
 | 
			
		||||
package testclient
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
 | 
			
		||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/registered"
 | 
			
		||||
@@ -48,10 +50,11 @@ type ReactionFunc func(FakeAction) (runtime.Object, error)
 | 
			
		||||
// Fake implements client.Interface. Meant to be embedded into a struct to get a default
 | 
			
		||||
// implementation. This makes faking out just the method you want to test easier.
 | 
			
		||||
type Fake struct {
 | 
			
		||||
	Actions []FakeAction
 | 
			
		||||
	Watch   watch.Interface
 | 
			
		||||
	Err     error
 | 
			
		||||
	sync.RWMutex
 | 
			
		||||
	actions []FakeAction
 | 
			
		||||
	err     error
 | 
			
		||||
 | 
			
		||||
	Watch watch.Interface
 | 
			
		||||
	// ReactFn is an optional function that will be invoked with the provided action
 | 
			
		||||
	// and return a response. It can implement scenario specific behavior. The type
 | 
			
		||||
	// of object returned must match the expected type from the caller (even if nil).
 | 
			
		||||
@@ -61,11 +64,47 @@ type Fake struct {
 | 
			
		||||
// Invokes records the provided FakeAction and then invokes the ReactFn (if provided).
 | 
			
		||||
// obj is expected to be of the same type a normal call would return.
 | 
			
		||||
func (c *Fake) Invokes(action FakeAction, obj runtime.Object) (runtime.Object, error) {
 | 
			
		||||
	c.Actions = append(c.Actions, action)
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	c.actions = append(c.actions, action)
 | 
			
		||||
	if c.ReactFn != nil {
 | 
			
		||||
		return c.ReactFn(action)
 | 
			
		||||
	}
 | 
			
		||||
	return obj, c.Err
 | 
			
		||||
	return obj, c.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClearActions clears the history of actions called on the fake client
 | 
			
		||||
func (c *Fake) ClearActions() {
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	c.Unlock()
 | 
			
		||||
 | 
			
		||||
	c.actions = make([]FakeAction, 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Actions returns a chronologically ordered slice fake actions called on the fake client
 | 
			
		||||
func (c *Fake) Actions() []FakeAction {
 | 
			
		||||
	c.RLock()
 | 
			
		||||
	defer c.RUnlock()
 | 
			
		||||
	fa := make([]FakeAction, len(c.actions))
 | 
			
		||||
	copy(fa, c.actions)
 | 
			
		||||
	return fa
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetErr sets the error to return for client calls
 | 
			
		||||
func (c *Fake) SetErr(err error) {
 | 
			
		||||
	c.Lock()
 | 
			
		||||
	defer c.Unlock()
 | 
			
		||||
 | 
			
		||||
	c.err = err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Err returns any a client error or nil
 | 
			
		||||
func (c *Fake) Err() error {
 | 
			
		||||
	c.RLock()
 | 
			
		||||
	c.RUnlock()
 | 
			
		||||
 | 
			
		||||
	return c.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Fake) LimitRanges(namespace string) client.LimitRangeInterface {
 | 
			
		||||
@@ -125,13 +164,13 @@ func (c *Fake) Namespaces() client.NamespaceInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Fake) ServerVersion() (*version.Info, error) {
 | 
			
		||||
	c.Actions = append(c.Actions, FakeAction{Action: "get-version", Value: nil})
 | 
			
		||||
	c.Invokes(FakeAction{Action: "get-version", Value: nil}, nil)
 | 
			
		||||
	versionInfo := version.Get()
 | 
			
		||||
	return &versionInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Fake) ServerAPIVersions() (*api.APIVersions, error) {
 | 
			
		||||
	c.Actions = append(c.Actions, FakeAction{Action: "get-apiversions", Value: nil})
 | 
			
		||||
	c.Invokes(FakeAction{Action: "get-apiversions", Value: nil}, nil)
 | 
			
		||||
	return &api.APIVersions{Versions: registered.RegisteredVersions}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1694,10 +1694,9 @@ func (s *AWSCloud) ensureSecurityGroup(name string, description string, vpcID st
 | 
			
		||||
		createResponse, err := s.ec2.CreateSecurityGroup(createRequest)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ignore := false
 | 
			
		||||
			switch err.(type) {
 | 
			
		||||
			switch err := err.(type) {
 | 
			
		||||
			case awserr.Error:
 | 
			
		||||
				awsError := err.(awserr.Error)
 | 
			
		||||
				if awsError.Code() == "InvalidGroup.Duplicate" && attempt < MaxReadThenCreateRetries {
 | 
			
		||||
				if err.Code() == "InvalidGroup.Duplicate" && attempt < MaxReadThenCreateRetries {
 | 
			
		||||
					glog.V(2).Infof("Got InvalidGroup.Duplicate while creating security group (race?); will retry")
 | 
			
		||||
					ignore = true
 | 
			
		||||
				}
 | 
			
		||||
 
 | 
			
		||||
@@ -247,7 +247,7 @@ func (nc *NodeController) tryUpdateNodeStatus(node *api.Node) (time.Duration, ap
 | 
			
		||||
	// - both saved and current statuses have Ready Conditions, different LastProbeTimes and different Ready Condition State -
 | 
			
		||||
	//   Ready Condition changed it state since we last seen it, so we update both probeTimestamp and readyTransitionTimestamp.
 | 
			
		||||
	// TODO: things to consider:
 | 
			
		||||
	//   - if 'LastProbeTime' have gone back in time its probably and error, currently we ignore it,
 | 
			
		||||
	//   - if 'LastProbeTime' have gone back in time its probably an error, currently we ignore it,
 | 
			
		||||
	//   - currently only correct Ready State transition outside of Node Controller is marking it ready by Kubelet, we don't check
 | 
			
		||||
	//     if that's the case, but it does not seem necessary.
 | 
			
		||||
	savedCondition := nc.getCondition(&savedNodeStatus.status, api.NodeReady)
 | 
			
		||||
@@ -374,18 +374,20 @@ func (nc *NodeController) monitorNodeStatus() error {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		decisionTimestamp := nc.now()
 | 
			
		||||
 | 
			
		||||
		if readyCondition != nil {
 | 
			
		||||
			// Check eviction timeout.
 | 
			
		||||
			// Check eviction timeout against decisionTimestamp
 | 
			
		||||
			if lastReadyCondition.Status == api.ConditionFalse &&
 | 
			
		||||
				nc.now().After(nc.nodeStatusMap[node.Name].readyTransitionTimestamp.Add(nc.podEvictionTimeout)) {
 | 
			
		||||
				decisionTimestamp.After(nc.nodeStatusMap[node.Name].readyTransitionTimestamp.Add(nc.podEvictionTimeout)) {
 | 
			
		||||
				if nc.podEvictor.AddNodeToEvict(node.Name) {
 | 
			
		||||
					glog.Infof("Adding pods to evict: %v is later than %v + %v", nc.now(), nc.nodeStatusMap[node.Name].readyTransitionTimestamp, nc.podEvictionTimeout)
 | 
			
		||||
					glog.Infof("Adding pods to evict: %v is later than %v + %v", decisionTimestamp, nc.nodeStatusMap[node.Name].readyTransitionTimestamp, nc.podEvictionTimeout)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if lastReadyCondition.Status == api.ConditionUnknown &&
 | 
			
		||||
				nc.now().After(nc.nodeStatusMap[node.Name].probeTimestamp.Add(nc.podEvictionTimeout-gracePeriod)) {
 | 
			
		||||
				decisionTimestamp.After(nc.nodeStatusMap[node.Name].probeTimestamp.Add(nc.podEvictionTimeout-gracePeriod)) {
 | 
			
		||||
				if nc.podEvictor.AddNodeToEvict(node.Name) {
 | 
			
		||||
					glog.Infof("Adding pods to evict2: %v is later than %v + %v", nc.now(), nc.nodeStatusMap[node.Name].readyTransitionTimestamp, nc.podEvictionTimeout-gracePeriod)
 | 
			
		||||
					glog.Infof("Adding pods to evict2: %v is later than %v + %v", decisionTimestamp, nc.nodeStatusMap[node.Name].readyTransitionTimestamp, nc.podEvictionTimeout-gracePeriod)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if lastReadyCondition.Status == api.ConditionTrue {
 | 
			
		||||
 
 | 
			
		||||
@@ -342,7 +342,7 @@ func TestMonitorNodeStatusEvictPods(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
		podEvictor.TryEvict(func(nodeName string) { nodeController.deletePods(nodeName) })
 | 
			
		||||
		podEvicted := false
 | 
			
		||||
		for _, action := range item.fakeNodeHandler.Actions {
 | 
			
		||||
		for _, action := range item.fakeNodeHandler.Actions() {
 | 
			
		||||
			if action.Action == "delete-pod" {
 | 
			
		||||
				podEvicted = true
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -93,20 +93,21 @@ func TestCreateExternalLoadBalancer(t *testing.T) {
 | 
			
		||||
		client := &testclient.Fake{}
 | 
			
		||||
		controller := New(cloud, client, "test-cluster")
 | 
			
		||||
		controller.init()
 | 
			
		||||
		cloud.Calls = nil    // ignore any cloud calls made in init()
 | 
			
		||||
		client.Actions = nil // ignore any client calls made in init()
 | 
			
		||||
		cloud.Calls = nil     // ignore any cloud calls made in init()
 | 
			
		||||
		client.ClearActions() // ignore any client calls made in init()
 | 
			
		||||
		err, _ := controller.createLoadBalancerIfNeeded(types.NamespacedName{"foo", "bar"}, item.service, nil)
 | 
			
		||||
		if !item.expectErr && err != nil {
 | 
			
		||||
			t.Errorf("unexpected error: %v", err)
 | 
			
		||||
		} else if item.expectErr && err == nil {
 | 
			
		||||
			t.Errorf("expected error creating %v, got nil", item.service)
 | 
			
		||||
		}
 | 
			
		||||
		actions := client.Actions()
 | 
			
		||||
		if !item.expectCreateAttempt {
 | 
			
		||||
			if len(cloud.Calls) > 0 {
 | 
			
		||||
				t.Errorf("unexpected cloud provider calls: %v", cloud.Calls)
 | 
			
		||||
			}
 | 
			
		||||
			if len(client.Actions) > 0 {
 | 
			
		||||
				t.Errorf("unexpected client actions: %v", client.Actions)
 | 
			
		||||
			if len(actions) > 0 {
 | 
			
		||||
				t.Errorf("unexpected client actions: %v", actions)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if len(cloud.Balancers) != 1 {
 | 
			
		||||
@@ -117,13 +118,13 @@ func TestCreateExternalLoadBalancer(t *testing.T) {
 | 
			
		||||
				t.Errorf("created load balancer has incorrect parameters: %v", cloud.Balancers[0])
 | 
			
		||||
			}
 | 
			
		||||
			actionFound := false
 | 
			
		||||
			for _, action := range client.Actions {
 | 
			
		||||
			for _, action := range actions {
 | 
			
		||||
				if action.Action == "update-service" {
 | 
			
		||||
					actionFound = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !actionFound {
 | 
			
		||||
				t.Errorf("expected updated service to be sent to client, got these actions instead: %v", client.Actions)
 | 
			
		||||
				t.Errorf("expected updated service to be sent to client, got these actions instead: %v", actions)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -191,9 +191,9 @@ func (rm *ReplicationManager) Run(workers int, stopCh <-chan struct{}) {
 | 
			
		||||
	rm.queue.ShutDown()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getPodControllers returns the controller managing the given pod.
 | 
			
		||||
// getPodController returns the controller managing the given pod.
 | 
			
		||||
// TODO: Surface that we are ignoring multiple controllers for a single pod.
 | 
			
		||||
func (rm *ReplicationManager) getPodControllers(pod *api.Pod) *api.ReplicationController {
 | 
			
		||||
func (rm *ReplicationManager) getPodController(pod *api.Pod) *api.ReplicationController {
 | 
			
		||||
	controllers, err := rm.rcStore.GetPodControllers(pod)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		glog.V(4).Infof("No controllers found for pod %v, replication manager will avoid syncing", pod.Name)
 | 
			
		||||
@@ -211,7 +211,7 @@ func (rm *ReplicationManager) getPodControllers(pod *api.Pod) *api.ReplicationCo
 | 
			
		||||
// When a pod is created, enqueue the controller that manages it and update it's expectations.
 | 
			
		||||
func (rm *ReplicationManager) addPod(obj interface{}) {
 | 
			
		||||
	pod := obj.(*api.Pod)
 | 
			
		||||
	if rc := rm.getPodControllers(pod); rc != nil {
 | 
			
		||||
	if rc := rm.getPodController(pod); rc != nil {
 | 
			
		||||
		rcKey, err := controller.KeyFunc(rc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err)
 | 
			
		||||
@@ -232,7 +232,7 @@ func (rm *ReplicationManager) updatePod(old, cur interface{}) {
 | 
			
		||||
	}
 | 
			
		||||
	// TODO: Write a unittest for this case
 | 
			
		||||
	curPod := cur.(*api.Pod)
 | 
			
		||||
	if rc := rm.getPodControllers(curPod); rc != nil {
 | 
			
		||||
	if rc := rm.getPodController(curPod); rc != nil {
 | 
			
		||||
		rm.enqueueController(rc)
 | 
			
		||||
	}
 | 
			
		||||
	oldPod := old.(*api.Pod)
 | 
			
		||||
@@ -240,7 +240,7 @@ func (rm *ReplicationManager) updatePod(old, cur interface{}) {
 | 
			
		||||
	if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) {
 | 
			
		||||
		// If the old and new rc are the same, the first one that syncs
 | 
			
		||||
		// will set expectations preventing any damage from the second.
 | 
			
		||||
		if oldRC := rm.getPodControllers(oldPod); oldRC != nil {
 | 
			
		||||
		if oldRC := rm.getPodController(oldPod); oldRC != nil {
 | 
			
		||||
			rm.enqueueController(oldRC)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -267,7 +267,7 @@ func (rm *ReplicationManager) deletePod(obj interface{}) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if rc := rm.getPodControllers(pod); rc != nil {
 | 
			
		||||
	if rc := rm.getPodController(pod); rc != nil {
 | 
			
		||||
		rcKey, err := controller.KeyFunc(rc)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			glog.Errorf("Couldn't get key for replication controller %#v: %v", rc, err)
 | 
			
		||||
 
 | 
			
		||||
@@ -484,7 +484,7 @@ func TestPodControllerLookup(t *testing.T) {
 | 
			
		||||
		for _, r := range c.inRCs {
 | 
			
		||||
			manager.rcStore.Add(r)
 | 
			
		||||
		}
 | 
			
		||||
		if rc := manager.getPodControllers(c.pod); rc != nil {
 | 
			
		||||
		if rc := manager.getPodController(c.pod); rc != nil {
 | 
			
		||||
			if c.outRCName != rc.Name {
 | 
			
		||||
				t.Errorf("Got controller %+v expected %+v", rc.Name, c.outRCName)
 | 
			
		||||
			}
 | 
			
		||||
@@ -693,7 +693,7 @@ func TestControllerUpdateStatusWithFailure(t *testing.T) {
 | 
			
		||||
	numReplicas := 10
 | 
			
		||||
	updateReplicaCount(fakeRCClient, *rc, numReplicas)
 | 
			
		||||
	updates, gets := 0, 0
 | 
			
		||||
	for _, a := range fakeClient.Actions {
 | 
			
		||||
	for _, a := range fakeClient.Actions() {
 | 
			
		||||
		switch a.Action {
 | 
			
		||||
		case testclient.GetControllerAction:
 | 
			
		||||
			gets++
 | 
			
		||||
 
 | 
			
		||||
@@ -187,8 +187,9 @@ func (o *PathOptions) GetExplicitFile() string {
 | 
			
		||||
// uses the default destination file to write the results into.  This results in multiple file reads, but it's very easy to follow.
 | 
			
		||||
// Preferences and CurrentContext should always be set in the default destination file.  Since we can't distinguish between empty and missing values
 | 
			
		||||
// (no nil strings), we're forced have separate handling for them.  In the kubeconfig cases, newConfig should have at most one difference,
 | 
			
		||||
// that means that this code will only write into a single file.
 | 
			
		||||
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) error {
 | 
			
		||||
// that means that this code will only write into a single file.  If you want to relativizePaths, you must provide a fully qualified path in any
 | 
			
		||||
// modified element.
 | 
			
		||||
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
 | 
			
		||||
	startingConfig, err := configAccess.GetStartingConfig()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -223,7 +224,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			configToWrite := getConfigFromFileOrDie(destinationFile)
 | 
			
		||||
			configToWrite.Clusters[key] = cluster
 | 
			
		||||
			t := *cluster
 | 
			
		||||
			configToWrite.Clusters[key] = &t
 | 
			
		||||
			configToWrite.Clusters[key].LocationOfOrigin = destinationFile
 | 
			
		||||
			if relativizePaths {
 | 
			
		||||
				if err := clientcmd.RelativizeClusterLocalPaths(configToWrite.Clusters[key]); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
@@ -257,7 +265,14 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config) erro
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			configToWrite := getConfigFromFileOrDie(destinationFile)
 | 
			
		||||
			configToWrite.AuthInfos[key] = authInfo
 | 
			
		||||
			t := *authInfo
 | 
			
		||||
			configToWrite.AuthInfos[key] = &t
 | 
			
		||||
			configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
 | 
			
		||||
			if relativizePaths {
 | 
			
		||||
				if err := clientcmd.RelativizeAuthInfoLocalPaths(configToWrite.AuthInfos[key]); err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := clientcmd.WriteToFile(*configToWrite, destinationFile); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
@@ -34,11 +35,11 @@ import (
 | 
			
		||||
 | 
			
		||||
func newRedFederalCowHammerConfig() clientcmdapi.Config {
 | 
			
		||||
	return clientcmdapi.Config{
 | 
			
		||||
		AuthInfos: map[string]clientcmdapi.AuthInfo{
 | 
			
		||||
		AuthInfos: map[string]*clientcmdapi.AuthInfo{
 | 
			
		||||
			"red-user": {Token: "red-token"}},
 | 
			
		||||
		Clusters: map[string]clientcmdapi.Cluster{
 | 
			
		||||
		Clusters: map[string]*clientcmdapi.Cluster{
 | 
			
		||||
			"cow-cluster": {Server: "http://cow.org:8080"}},
 | 
			
		||||
		Contexts: map[string]clientcmdapi.Context{
 | 
			
		||||
		Contexts: map[string]*clientcmdapi.Context{
 | 
			
		||||
			"federal-context": {AuthInfo: "red-user", Cluster: "cow-cluster"}},
 | 
			
		||||
		CurrentContext: "federal-context",
 | 
			
		||||
	}
 | 
			
		||||
@@ -79,10 +80,9 @@ func TestSetCurrentContext(t *testing.T) {
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
 | 
			
		||||
	newContextName := "the-new-context"
 | 
			
		||||
	newContext := clientcmdapi.NewContext()
 | 
			
		||||
 | 
			
		||||
	startingConfig.Contexts[newContextName] = *newContext
 | 
			
		||||
	expectedConfig.Contexts[newContextName] = *newContext
 | 
			
		||||
	startingConfig.Contexts[newContextName] = clientcmdapi.NewContext()
 | 
			
		||||
	expectedConfig.Contexts[newContextName] = clientcmdapi.NewContext()
 | 
			
		||||
 | 
			
		||||
	expectedConfig.CurrentContext = newContextName
 | 
			
		||||
 | 
			
		||||
@@ -108,10 +108,7 @@ func TestSetNonExistantContext(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestSetIntoExistingStruct(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	a := expectedConfig.AuthInfos["red-user"]
 | 
			
		||||
	authInfo := &a
 | 
			
		||||
	authInfo.Password = "new-path-value"
 | 
			
		||||
	expectedConfig.AuthInfos["red-user"] = *authInfo
 | 
			
		||||
	expectedConfig.AuthInfos["red-user"].Password = "new-path-value"
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set", "users.red-user.password", "new-path-value"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
@@ -123,10 +120,7 @@ func TestSetIntoExistingStruct(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestSetWithPathPrefixIntoExistingStruct(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	cc := expectedConfig.Clusters["cow-clusters"]
 | 
			
		||||
	cinfo := &cc
 | 
			
		||||
	cinfo.Server = "http://cow.org:8080/foo/baz"
 | 
			
		||||
	expectedConfig.Clusters["cow-cluster"] = *cinfo
 | 
			
		||||
	expectedConfig.Clusters["cow-cluster"].Server = "http://cow.org:8080/foo/baz"
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set", "clusters.cow-cluster.server", "http://cow.org:8080/foo/baz"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
@@ -164,7 +158,7 @@ func TestUnsetStruct(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestUnsetField(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.AuthInfos["red-user"] = *clientcmdapi.NewAuthInfo()
 | 
			
		||||
	expectedConfig.AuthInfos["red-user"] = clientcmdapi.NewAuthInfo()
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"unset", "users.red-user.token"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
@@ -178,7 +172,7 @@ func TestSetIntoNewStruct(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	cluster := clientcmdapi.NewCluster()
 | 
			
		||||
	cluster.Server = "new-server-value"
 | 
			
		||||
	expectedConfig.Clusters["big-cluster"] = *cluster
 | 
			
		||||
	expectedConfig.Clusters["big-cluster"] = cluster
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set", "clusters.big-cluster.server", "new-server-value"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
@@ -192,7 +186,7 @@ func TestSetBoolean(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	cluster := clientcmdapi.NewCluster()
 | 
			
		||||
	cluster.InsecureSkipTLSVerify = true
 | 
			
		||||
	expectedConfig.Clusters["big-cluster"] = *cluster
 | 
			
		||||
	expectedConfig.Clusters["big-cluster"] = cluster
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set", "clusters.big-cluster.insecure-skip-tls-verify", "true"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
@@ -206,7 +200,7 @@ func TestSetIntoNewConfig(t *testing.T) {
 | 
			
		||||
	expectedConfig := *clientcmdapi.NewConfig()
 | 
			
		||||
	context := clientcmdapi.NewContext()
 | 
			
		||||
	context.AuthInfo = "fake-user"
 | 
			
		||||
	expectedConfig.Contexts["new-context"] = *context
 | 
			
		||||
	expectedConfig.Contexts["new-context"] = context
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set", "contexts.new-context.user", "fake-user"},
 | 
			
		||||
		startingConfig: *clientcmdapi.NewConfig(),
 | 
			
		||||
@@ -218,7 +212,7 @@ func TestSetIntoNewConfig(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestNewEmptyAuth(t *testing.T) {
 | 
			
		||||
	expectedConfig := *clientcmdapi.NewConfig()
 | 
			
		||||
	expectedConfig.AuthInfos["the-user-name"] = *clientcmdapi.NewAuthInfo()
 | 
			
		||||
	expectedConfig.AuthInfos["the-user-name"] = clientcmdapi.NewAuthInfo()
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "the-user-name"},
 | 
			
		||||
		startingConfig: *clientcmdapi.NewConfig(),
 | 
			
		||||
@@ -232,7 +226,7 @@ func TestAdditionalAuth(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	authInfo := clientcmdapi.NewAuthInfo()
 | 
			
		||||
	authInfo.Token = "token"
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfo
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfo
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
@@ -250,7 +244,7 @@ func TestEmbedClientCert(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	authInfo := clientcmdapi.NewAuthInfo()
 | 
			
		||||
	authInfo.ClientCertificateData = fakeData
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfo
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfo
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
 | 
			
		||||
@@ -269,7 +263,7 @@ func TestEmbedClientKey(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	authInfo := clientcmdapi.NewAuthInfo()
 | 
			
		||||
	authInfo.ClientKeyData = fakeData
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfo
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfo
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagKeyFile + "=" + fakeKeyFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
 | 
			
		||||
@@ -293,13 +287,15 @@ func TestEmbedNoKeyOrCertDisallowed(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEmptyTokenAndCertAllowed(t *testing.T) {
 | 
			
		||||
	fakeCertFile, _ := ioutil.TempFile("", "cert-file")
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	authInfo := clientcmdapi.NewAuthInfo()
 | 
			
		||||
	authInfo.ClientCertificate = "cert-file"
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfo
 | 
			
		||||
	authInfo.ClientCertificate = path.Base(fakeCertFile.Name())
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfo
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert-file", "--" + clientcmd.FlagBearerToken + "="},
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=" + fakeCertFile.Name(), "--" + clientcmd.FlagBearerToken + "="},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -311,10 +307,10 @@ func TestTokenAndCertAllowed(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	authInfo := clientcmdapi.NewAuthInfo()
 | 
			
		||||
	authInfo.Token = "token"
 | 
			
		||||
	authInfo.ClientCertificate = "cert-file"
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfo
 | 
			
		||||
	authInfo.ClientCertificate = "/cert-file"
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfo
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert-file", "--" + clientcmd.FlagBearerToken + "=token"},
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert-file", "--" + clientcmd.FlagBearerToken + "=token"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -343,10 +339,10 @@ func TestBasicClearsToken(t *testing.T) {
 | 
			
		||||
	authInfoWithBasic.Password = "mypass"
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = *authInfoWithToken
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = authInfoWithToken
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfoWithBasic
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfoWithBasic
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagUsername + "=myuser", "--" + clientcmd.FlagPassword + "=mypass"},
 | 
			
		||||
@@ -366,10 +362,10 @@ func TestTokenClearsBasic(t *testing.T) {
 | 
			
		||||
	authInfoWithToken.Token = "token"
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = *authInfoWithBasic
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = authInfoWithBasic
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfoWithToken
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfoWithToken
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
 | 
			
		||||
@@ -395,10 +391,10 @@ func TestTokenLeavesCert(t *testing.T) {
 | 
			
		||||
	authInfoWithTokenAndCerts.ClientKeyData = []byte("keydata")
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = *authInfoWithCerts
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = authInfoWithCerts
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfoWithTokenAndCerts
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagBearerToken + "=token"},
 | 
			
		||||
@@ -415,17 +411,17 @@ func TestCertLeavesToken(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	authInfoWithTokenAndCerts := clientcmdapi.NewAuthInfo()
 | 
			
		||||
	authInfoWithTokenAndCerts.Token = "token"
 | 
			
		||||
	authInfoWithTokenAndCerts.ClientCertificate = "cert"
 | 
			
		||||
	authInfoWithTokenAndCerts.ClientKey = "key"
 | 
			
		||||
	authInfoWithTokenAndCerts.ClientCertificate = "/cert"
 | 
			
		||||
	authInfoWithTokenAndCerts.ClientKey = "/key"
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = *authInfoWithToken
 | 
			
		||||
	startingConfig.AuthInfos["another-user"] = authInfoWithToken
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = *authInfoWithTokenAndCerts
 | 
			
		||||
	expectedConfig.AuthInfos["another-user"] = authInfoWithTokenAndCerts
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=cert", "--" + clientcmd.FlagKeyFile + "=key"},
 | 
			
		||||
		args:           []string{"set-credentials", "another-user", "--" + clientcmd.FlagCertFile + "=/cert", "--" + clientcmd.FlagKeyFile + "=/key"},
 | 
			
		||||
		startingConfig: startingConfig,
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -434,20 +430,22 @@ func TestCertLeavesToken(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCAClearsInsecure(t *testing.T) {
 | 
			
		||||
	fakeCAFile, _ := ioutil.TempFile("", "ca-file")
 | 
			
		||||
 | 
			
		||||
	clusterInfoWithInsecure := clientcmdapi.NewCluster()
 | 
			
		||||
	clusterInfoWithInsecure.InsecureSkipTLSVerify = true
 | 
			
		||||
 | 
			
		||||
	clusterInfoWithCA := clientcmdapi.NewCluster()
 | 
			
		||||
	clusterInfoWithCA.CertificateAuthority = "cafile"
 | 
			
		||||
	clusterInfoWithCA.CertificateAuthority = path.Base(fakeCAFile.Name())
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = *clusterInfoWithInsecure
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = *clusterInfoWithCA
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile"},
 | 
			
		||||
		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name()},
 | 
			
		||||
		startingConfig: startingConfig,
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -460,16 +458,16 @@ func TestCAClearsCAData(t *testing.T) {
 | 
			
		||||
	clusterInfoWithCAData.CertificateAuthorityData = []byte("cadata")
 | 
			
		||||
 | 
			
		||||
	clusterInfoWithCA := clientcmdapi.NewCluster()
 | 
			
		||||
	clusterInfoWithCA.CertificateAuthority = "cafile"
 | 
			
		||||
	clusterInfoWithCA.CertificateAuthority = "/cafile"
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = *clusterInfoWithCAData
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = clusterInfoWithCAData
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = *clusterInfoWithCA
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCA
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=cafile", "--" + clientcmd.FlagInsecure + "=false"},
 | 
			
		||||
		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=/cafile", "--" + clientcmd.FlagInsecure + "=false"},
 | 
			
		||||
		startingConfig: startingConfig,
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -486,10 +484,10 @@ func TestInsecureClearsCA(t *testing.T) {
 | 
			
		||||
	clusterInfoWithCA.CertificateAuthorityData = []byte("cadata")
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = *clusterInfoWithCA
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = clusterInfoWithCA
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = *clusterInfoWithInsecure
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = clusterInfoWithInsecure
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagInsecure + "=true"},
 | 
			
		||||
@@ -513,10 +511,10 @@ func TestCADataClearsCA(t *testing.T) {
 | 
			
		||||
	clusterInfoWithCA.CertificateAuthority = "cafile"
 | 
			
		||||
 | 
			
		||||
	startingConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = *clusterInfoWithCA
 | 
			
		||||
	startingConfig.Clusters["another-cluster"] = clusterInfoWithCA
 | 
			
		||||
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = *clusterInfoWithCAData
 | 
			
		||||
	expectedConfig.Clusters["another-cluster"] = clusterInfoWithCAData
 | 
			
		||||
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-cluster", "another-cluster", "--" + clientcmd.FlagCAFile + "=" + fakeCAFile.Name(), "--" + clientcmd.FlagEmbedCerts + "=true"},
 | 
			
		||||
@@ -553,10 +551,10 @@ func TestCAAndInsecureDisallowed(t *testing.T) {
 | 
			
		||||
func TestMergeExistingAuth(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	authInfo := expectedConfig.AuthInfos["red-user"]
 | 
			
		||||
	authInfo.ClientKey = "key"
 | 
			
		||||
	authInfo.ClientKey = "/key"
 | 
			
		||||
	expectedConfig.AuthInfos["red-user"] = authInfo
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-credentials", "red-user", "--" + clientcmd.FlagKeyFile + "=key"},
 | 
			
		||||
		args:           []string{"set-credentials", "red-user", "--" + clientcmd.FlagKeyFile + "=/key"},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -566,7 +564,7 @@ func TestMergeExistingAuth(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestNewEmptyCluster(t *testing.T) {
 | 
			
		||||
	expectedConfig := *clientcmdapi.NewConfig()
 | 
			
		||||
	expectedConfig.Clusters["new-cluster"] = *clientcmdapi.NewCluster()
 | 
			
		||||
	expectedConfig.Clusters["new-cluster"] = clientcmdapi.NewCluster()
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-cluster", "new-cluster"},
 | 
			
		||||
		startingConfig: *clientcmdapi.NewConfig(),
 | 
			
		||||
@@ -578,14 +576,14 @@ func TestNewEmptyCluster(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestAdditionalCluster(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	cluster := *clientcmdapi.NewCluster()
 | 
			
		||||
	cluster := clientcmdapi.NewCluster()
 | 
			
		||||
	cluster.APIVersion = testapi.Version()
 | 
			
		||||
	cluster.CertificateAuthority = "ca-location"
 | 
			
		||||
	cluster.CertificateAuthority = "/ca-location"
 | 
			
		||||
	cluster.InsecureSkipTLSVerify = false
 | 
			
		||||
	cluster.Server = "serverlocation"
 | 
			
		||||
	expectedConfig.Clusters["different-cluster"] = cluster
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=ca-location", "--" + clientcmd.FlagAPIVersion + "=" + testapi.Version()},
 | 
			
		||||
		args:           []string{"set-cluster", "different-cluster", "--" + clientcmd.FlagAPIServer + "=serverlocation", "--" + clientcmd.FlagInsecure + "=false", "--" + clientcmd.FlagCAFile + "=/ca-location", "--" + clientcmd.FlagAPIVersion + "=" + testapi.Version()},
 | 
			
		||||
		startingConfig: newRedFederalCowHammerConfig(),
 | 
			
		||||
		expectedConfig: expectedConfig,
 | 
			
		||||
	}
 | 
			
		||||
@@ -595,7 +593,7 @@ func TestAdditionalCluster(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestOverwriteExistingCluster(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	cluster := *clientcmdapi.NewCluster()
 | 
			
		||||
	cluster := clientcmdapi.NewCluster()
 | 
			
		||||
	cluster.Server = "serverlocation"
 | 
			
		||||
	expectedConfig.Clusters["cow-cluster"] = cluster
 | 
			
		||||
 | 
			
		||||
@@ -610,7 +608,7 @@ func TestOverwriteExistingCluster(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestNewEmptyContext(t *testing.T) {
 | 
			
		||||
	expectedConfig := *clientcmdapi.NewConfig()
 | 
			
		||||
	expectedConfig.Contexts["new-context"] = *clientcmdapi.NewContext()
 | 
			
		||||
	expectedConfig.Contexts["new-context"] = clientcmdapi.NewContext()
 | 
			
		||||
	test := configCommandTest{
 | 
			
		||||
		args:           []string{"set-context", "new-context"},
 | 
			
		||||
		startingConfig: *clientcmdapi.NewConfig(),
 | 
			
		||||
@@ -622,7 +620,7 @@ func TestNewEmptyContext(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestAdditionalContext(t *testing.T) {
 | 
			
		||||
	expectedConfig := newRedFederalCowHammerConfig()
 | 
			
		||||
	context := *clientcmdapi.NewContext()
 | 
			
		||||
	context := clientcmdapi.NewContext()
 | 
			
		||||
	context.Cluster = "some-cluster"
 | 
			
		||||
	context.AuthInfo = "some-user"
 | 
			
		||||
	context.Namespace = "different-namespace"
 | 
			
		||||
@@ -683,10 +681,13 @@ func TestToBool(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testConfigCommand(args []string, startingConfig clientcmdapi.Config) (string, clientcmdapi.Config) {
 | 
			
		||||
func testConfigCommand(args []string, startingConfig clientcmdapi.Config, t *testing.T) (string, clientcmdapi.Config) {
 | 
			
		||||
	fakeKubeFile, _ := ioutil.TempFile("", "")
 | 
			
		||||
	defer os.Remove(fakeKubeFile.Name())
 | 
			
		||||
	clientcmd.WriteToFile(startingConfig, fakeKubeFile.Name())
 | 
			
		||||
	err := clientcmd.WriteToFile(startingConfig, fakeKubeFile.Name())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	argsToUse := make([]string, 0, 2+len(args))
 | 
			
		||||
	argsToUse = append(argsToUse, "--kubeconfig="+fakeKubeFile.Name())
 | 
			
		||||
@@ -712,7 +713,7 @@ type configCommandTest struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (test configCommandTest) run(t *testing.T) string {
 | 
			
		||||
	out, actualConfig := testConfigCommand(test.args, test.startingConfig)
 | 
			
		||||
	out, actualConfig := testConfigCommand(test.args, test.startingConfig, t)
 | 
			
		||||
 | 
			
		||||
	testSetNilMapsToEmpties(reflect.ValueOf(&test.expectedConfig))
 | 
			
		||||
	testSetNilMapsToEmpties(reflect.ValueOf(&actualConfig))
 | 
			
		||||
@@ -755,20 +756,7 @@ func testSetNilMapsToEmpties(curr reflect.Value) {
 | 
			
		||||
	case reflect.Map:
 | 
			
		||||
		for _, mapKey := range actualCurrValue.MapKeys() {
 | 
			
		||||
			currMapValue := actualCurrValue.MapIndex(mapKey)
 | 
			
		||||
 | 
			
		||||
			// our maps do not hold pointers to structs, they hold the structs themselves.  This means that MapIndex returns the struct itself
 | 
			
		||||
			// That in turn means that they have kinds of type.Struct, which is not a settable type.  Because of this, we need to make new struct of that type
 | 
			
		||||
			// copy all the data from the old value into the new value, then take the .addr of the new value to modify it in the next recursion.
 | 
			
		||||
			// clear as mud
 | 
			
		||||
			modifiableMapValue := reflect.New(currMapValue.Type()).Elem()
 | 
			
		||||
			modifiableMapValue.Set(currMapValue)
 | 
			
		||||
 | 
			
		||||
			if modifiableMapValue.Kind() == reflect.Struct {
 | 
			
		||||
				modifiableMapValue = modifiableMapValue.Addr()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			testSetNilMapsToEmpties(modifiableMapValue)
 | 
			
		||||
			actualCurrValue.SetMapIndex(mapKey, reflect.Indirect(modifiableMapValue))
 | 
			
		||||
			testSetNilMapsToEmpties(currMapValue)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case reflect.Struct:
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
@@ -108,10 +109,14 @@ func (o createAuthInfoOptions) run() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	authInfo := o.modifyAuthInfo(config.AuthInfos[o.name])
 | 
			
		||||
	config.AuthInfos[o.name] = authInfo
 | 
			
		||||
	startingStanza, exists := config.AuthInfos[o.name]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		startingStanza = clientcmdapi.NewAuthInfo()
 | 
			
		||||
	}
 | 
			
		||||
	authInfo := o.modifyAuthInfo(*startingStanza)
 | 
			
		||||
	config.AuthInfos[o.name] = &authInfo
 | 
			
		||||
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config); err != nil {
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config, true); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -130,6 +135,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
 | 
			
		||||
			modifiedAuthInfo.ClientCertificateData, _ = ioutil.ReadFile(certPath)
 | 
			
		||||
			modifiedAuthInfo.ClientCertificate = ""
 | 
			
		||||
		} else {
 | 
			
		||||
			certPath, _ = filepath.Abs(certPath)
 | 
			
		||||
			modifiedAuthInfo.ClientCertificate = certPath
 | 
			
		||||
			if len(modifiedAuthInfo.ClientCertificate) > 0 {
 | 
			
		||||
				modifiedAuthInfo.ClientCertificateData = nil
 | 
			
		||||
@@ -142,6 +148,7 @@ func (o *createAuthInfoOptions) modifyAuthInfo(existingAuthInfo clientcmdapi.Aut
 | 
			
		||||
			modifiedAuthInfo.ClientKeyData, _ = ioutil.ReadFile(keyPath)
 | 
			
		||||
			modifiedAuthInfo.ClientKey = ""
 | 
			
		||||
		} else {
 | 
			
		||||
			keyPath, _ = filepath.Abs(keyPath)
 | 
			
		||||
			modifiedAuthInfo.ClientKey = keyPath
 | 
			
		||||
			if len(modifiedAuthInfo.ClientKey) > 0 {
 | 
			
		||||
				modifiedAuthInfo.ClientKeyData = nil
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
 | 
			
		||||
@@ -94,10 +95,14 @@ func (o createClusterOptions) run() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cluster := o.modifyCluster(config.Clusters[o.name])
 | 
			
		||||
	config.Clusters[o.name] = cluster
 | 
			
		||||
	startingStanza, exists := config.Clusters[o.name]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		startingStanza = clientcmdapi.NewCluster()
 | 
			
		||||
	}
 | 
			
		||||
	cluster := o.modifyCluster(*startingStanza)
 | 
			
		||||
	config.Clusters[o.name] = &cluster
 | 
			
		||||
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config); err != nil {
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config, true); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -129,6 +134,7 @@ func (o *createClusterOptions) modifyCluster(existingCluster clientcmdapi.Cluste
 | 
			
		||||
			modifiedCluster.InsecureSkipTLSVerify = false
 | 
			
		||||
			modifiedCluster.CertificateAuthority = ""
 | 
			
		||||
		} else {
 | 
			
		||||
			caPath, _ = filepath.Abs(caPath)
 | 
			
		||||
			modifiedCluster.CertificateAuthority = caPath
 | 
			
		||||
			// Specifying a certificate authority file clears certificate authority data and insecure mode
 | 
			
		||||
			if caPath != "" {
 | 
			
		||||
 
 | 
			
		||||
@@ -81,10 +81,14 @@ func (o createContextOptions) run() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	context := o.modifyContext(config.Contexts[o.name])
 | 
			
		||||
	config.Contexts[o.name] = context
 | 
			
		||||
	startingStanza, exists := config.Contexts[o.name]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		startingStanza = clientcmdapi.NewContext()
 | 
			
		||||
	}
 | 
			
		||||
	context := o.modifyContext(*startingStanza)
 | 
			
		||||
	config.Contexts[o.name] = &context
 | 
			
		||||
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config); err != nil {
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config, true); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ func newNavigationSteps(path string) (*navigationSteps, error) {
 | 
			
		||||
			// store them as a single step.  In order to do that, we need to determine what set of tokens is a legal step AFTER the name of the map key
 | 
			
		||||
			// This set of reflective code pulls the type of the map values, uses that type to look up the set of legal tags.  Those legal tags are used to
 | 
			
		||||
			// walk the list of remaining parts until we find a match to a legal tag or the end of the string.  That name is used to burn all the used parts.
 | 
			
		||||
			mapValueType := currType.Elem()
 | 
			
		||||
			mapValueType := currType.Elem().Elem()
 | 
			
		||||
			mapValueOptions, err := getPotentialTypeValues(mapValueType)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
@@ -120,6 +120,10 @@ func findNameStep(parts []string, typeOptions util.StringSet) string {
 | 
			
		||||
 | 
			
		||||
// getPotentialTypeValues takes a type and looks up the tags used to represent its fields when serialized.
 | 
			
		||||
func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, error) {
 | 
			
		||||
	if typeValue.Kind() == reflect.Ptr {
 | 
			
		||||
		typeValue = typeValue.Elem()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if typeValue.Kind() != reflect.Struct {
 | 
			
		||||
		return nil, fmt.Errorf("%v is not of type struct", typeValue)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ func TestParseWithDots(t *testing.T) {
 | 
			
		||||
		path: "clusters.my.dot.delimited.name.server",
 | 
			
		||||
		expectedNavigationSteps: navigationSteps{
 | 
			
		||||
			steps: []navigationStep{
 | 
			
		||||
				{"clusters", reflect.TypeOf(make(map[string]clientcmdapi.Cluster))},
 | 
			
		||||
				{"clusters", reflect.TypeOf(make(map[string]*clientcmdapi.Cluster))},
 | 
			
		||||
				{"my.dot.delimited.name", reflect.TypeOf(clientcmdapi.Cluster{})},
 | 
			
		||||
				{"server", reflect.TypeOf("")},
 | 
			
		||||
			},
 | 
			
		||||
@@ -51,7 +51,7 @@ func TestParseWithDotsEndingWithName(t *testing.T) {
 | 
			
		||||
		path: "contexts.10.12.12.12",
 | 
			
		||||
		expectedNavigationSteps: navigationSteps{
 | 
			
		||||
			steps: []navigationStep{
 | 
			
		||||
				{"contexts", reflect.TypeOf(make(map[string]clientcmdapi.Context))},
 | 
			
		||||
				{"contexts", reflect.TypeOf(make(map[string]*clientcmdapi.Context))},
 | 
			
		||||
				{"10.12.12.12", reflect.TypeOf(clientcmdapi.Context{})},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -91,5 +91,6 @@ func (test stepParserTest) run(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	if !reflect.DeepEqual(test.expectedNavigationSteps, *actualSteps) {
 | 
			
		||||
		t.Errorf("diff: %v", util.ObjectDiff(test.expectedNavigationSteps, *actualSteps))
 | 
			
		||||
		t.Errorf("expected: %#v\n actual:   %#v", test.expectedNavigationSteps, *actualSteps)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@ func (o setOptions) run() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config); err != nil {
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config, false); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -139,26 +139,15 @@ func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue stri
 | 
			
		||||
 | 
			
		||||
		needToSetNewMapValue := currMapValue.Kind() == reflect.Invalid
 | 
			
		||||
		if needToSetNewMapValue {
 | 
			
		||||
			currMapValue = reflect.New(mapValueType).Elem()
 | 
			
		||||
			currMapValue = reflect.New(mapValueType.Elem()).Elem().Addr()
 | 
			
		||||
			actualCurrValue.SetMapIndex(mapKey, currMapValue)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// our maps do not hold pointers to structs, they hold the structs themselves.  This means that MapIndex returns the struct itself
 | 
			
		||||
		// That in turn means that they have kinds of type.Struct, which is not a settable type.  Because of this, we need to make new struct of that type
 | 
			
		||||
		// copy all the data from the old value into the new value, then take the .addr of the new value to modify it in the next recursion.
 | 
			
		||||
		// clear as mud
 | 
			
		||||
		modifiableMapValue := reflect.New(currMapValue.Type()).Elem()
 | 
			
		||||
		modifiableMapValue.Set(currMapValue)
 | 
			
		||||
 | 
			
		||||
		if modifiableMapValue.Kind() == reflect.Struct {
 | 
			
		||||
			modifiableMapValue = modifiableMapValue.Addr()
 | 
			
		||||
		}
 | 
			
		||||
		err := modifyConfig(modifiableMapValue, steps, propertyValue, unset)
 | 
			
		||||
		err := modifyConfig(currMapValue, steps, propertyValue, unset)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		actualCurrValue.SetMapIndex(mapKey, reflect.Indirect(modifiableMapValue))
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	case reflect.String:
 | 
			
		||||
@@ -213,5 +202,6 @@ func modifyConfig(curr reflect.Value, steps *navigationSteps, propertyValue stri
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Errorf("Unrecognized type: %v", actualCurrValue)
 | 
			
		||||
	panic(fmt.Errorf("Unrecognized type: %v", actualCurrValue))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,7 @@ func (o unsetOptions) run() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config); err != nil {
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config, false); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ func (o useContextOptions) run() error {
 | 
			
		||||
 | 
			
		||||
	config.CurrentContext = o.contextName
 | 
			
		||||
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config); err != nil {
 | 
			
		||||
	if err := ModifyConfig(o.configAccess, *config, true); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,10 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	stop_long = `Gracefully shut down a resource by name or filename.
 | 
			
		||||
	stop_long = `Deprecated: Gracefully shut down a resource by name or filename.
 | 
			
		||||
 | 
			
		||||
stop command is deprecated, all its functionalities are covered by delete command.
 | 
			
		||||
See 'kubectl delete --help' for more details.
 | 
			
		||||
 | 
			
		||||
Attempts to shut down and delete a resource that supports graceful termination.
 | 
			
		||||
If the resource is scalable it will be scaled to 0 before deletion.`
 | 
			
		||||
@@ -50,7 +53,7 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command {
 | 
			
		||||
	}{}
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:     "stop (-f FILENAME | RESOURCE (NAME | -l label | --all))",
 | 
			
		||||
		Short:   "Gracefully shut down a resource by name or filename.",
 | 
			
		||||
		Short:   "Deprecated: Gracefully shut down a resource by name or filename.",
 | 
			
		||||
		Long:    stop_long,
 | 
			
		||||
		Example: stop_example,
 | 
			
		||||
		Run: func(cmd *cobra.Command, args []string) {
 | 
			
		||||
 
 | 
			
		||||
@@ -414,8 +414,7 @@ func TestTemplateStrings(t *testing.T) {
 | 
			
		||||
			"true",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	// The point of this test is to verify that the below template works. If you change this
 | 
			
		||||
	// template, you need to update hack/e2e-suite/update.sh.
 | 
			
		||||
	// The point of this test is to verify that the below template works.
 | 
			
		||||
	tmpl := `{{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "foo") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`
 | 
			
		||||
	p, err := NewTemplatePrinter([]byte(tmpl))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -74,19 +74,19 @@ func (c *fakeRc) Get(name string) (*api.ReplicationController, error) {
 | 
			
		||||
	if len(c.responses) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("Unexpected Action: %s", action)
 | 
			
		||||
	}
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, action)
 | 
			
		||||
	c.Fake.Invokes(action, nil)
 | 
			
		||||
	result := c.responses[0]
 | 
			
		||||
	c.responses = c.responses[1:]
 | 
			
		||||
	return result.controller, result.err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *fakeRc) Create(controller *api.ReplicationController) (*api.ReplicationController, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, testclient.FakeAction{Action: "create-controller", Value: controller.ObjectMeta.Name})
 | 
			
		||||
	c.Fake.Invokes(testclient.FakeAction{Action: "create-controller", Value: controller.ObjectMeta.Name}, nil)
 | 
			
		||||
	return controller, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *fakeRc) Update(controller *api.ReplicationController) (*api.ReplicationController, error) {
 | 
			
		||||
	c.Fake.Actions = append(c.Fake.Actions, testclient.FakeAction{Action: "update-controller", Value: controller.ObjectMeta.Name})
 | 
			
		||||
	c.Fake.Invokes(testclient.FakeAction{Action: "update-controller", Value: controller.ObjectMeta.Name}, nil)
 | 
			
		||||
	return controller, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -73,14 +73,15 @@ func TestReplicationControllerScale(t *testing.T) {
 | 
			
		||||
	name := "foo"
 | 
			
		||||
	scaler.Scale("default", name, count, &preconditions, nil, nil)
 | 
			
		||||
 | 
			
		||||
	if len(fake.Actions) != 2 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", fake.Actions)
 | 
			
		||||
	actions := fake.Actions()
 | 
			
		||||
	if len(actions) != 2 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions)
 | 
			
		||||
	}
 | 
			
		||||
	if fake.Actions[0].Action != "get-replicationController" || fake.Actions[0].Value != name {
 | 
			
		||||
		t.Errorf("unexpected action: %v, expected get-replicationController %s", fake.Actions[0], name)
 | 
			
		||||
	if actions[0].Action != "get-replicationController" || actions[0].Value != name {
 | 
			
		||||
		t.Errorf("unexpected action: %v, expected get-replicationController %s", actions[0], name)
 | 
			
		||||
	}
 | 
			
		||||
	if fake.Actions[1].Action != "update-replicationController" || fake.Actions[1].Value.(*api.ReplicationController).Spec.Replicas != int(count) {
 | 
			
		||||
		t.Errorf("unexpected action %v, expected update-replicationController with replicas = %d", fake.Actions[1], count)
 | 
			
		||||
	if actions[1].Action != "update-replicationController" || actions[1].Value.(*api.ReplicationController).Spec.Replicas != int(count) {
 | 
			
		||||
		t.Errorf("unexpected action %v, expected update-replicationController with replicas = %d", actions[1], count)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -96,11 +97,12 @@ func TestReplicationControllerScaleFailsPreconditions(t *testing.T) {
 | 
			
		||||
	name := "foo"
 | 
			
		||||
	scaler.Scale("default", name, count, &preconditions, nil, nil)
 | 
			
		||||
 | 
			
		||||
	if len(fake.Actions) != 1 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", fake.Actions)
 | 
			
		||||
	actions := fake.Actions()
 | 
			
		||||
	if len(actions) != 1 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v, expected 2 actions (get, update)", actions)
 | 
			
		||||
	}
 | 
			
		||||
	if fake.Actions[0].Action != "get-replicationController" || fake.Actions[0].Value != name {
 | 
			
		||||
		t.Errorf("unexpected action: %v, expected get-replicationController %s", fake.Actions[0], name)
 | 
			
		||||
	if actions[0].Action != "get-replicationController" || actions[0].Value != name {
 | 
			
		||||
		t.Errorf("unexpected action: %v, expected get-replicationController %s", actions[0], name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -47,12 +47,13 @@ func TestReplicationControllerStop(t *testing.T) {
 | 
			
		||||
	if s != expected {
 | 
			
		||||
		t.Errorf("expected %s, got %s", expected, s)
 | 
			
		||||
	}
 | 
			
		||||
	if len(fake.Actions) != 7 {
 | 
			
		||||
	actions := fake.Actions()
 | 
			
		||||
	if len(actions) != 7 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v, expected 6 actions (get, list, get, update, get, get, delete)", fake.Actions)
 | 
			
		||||
	}
 | 
			
		||||
	for i, action := range []string{"get", "list", "get", "update", "get", "get", "delete"} {
 | 
			
		||||
		if fake.Actions[i].Action != action+"-replicationController" {
 | 
			
		||||
			t.Errorf("unexpected action: %+v, expected %s-replicationController", fake.Actions[i], action)
 | 
			
		||||
		if actions[i].Action != action+"-replicationController" {
 | 
			
		||||
			t.Errorf("unexpected action: %+v, expected %s-replicationController", actions[i], action)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -159,10 +160,11 @@ func TestSimpleStop(t *testing.T) {
 | 
			
		||||
				t.Errorf("unexpected return: %s (%s)", s, test.test)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(test.actions) != len(fake.Actions) {
 | 
			
		||||
		actions := fake.Actions()
 | 
			
		||||
		if len(test.actions) != len(actions) {
 | 
			
		||||
			t.Errorf("unexpected actions: %v; expected %v (%s)", fake.Actions, test.actions, test.test)
 | 
			
		||||
		}
 | 
			
		||||
		for i, action := range fake.Actions {
 | 
			
		||||
		for i, action := range actions {
 | 
			
		||||
			testAction := test.actions[i]
 | 
			
		||||
			if action.Action != testAction {
 | 
			
		||||
				t.Errorf("unexpected action: %v; expected %v (%s)", action, testAction, test.test)
 | 
			
		||||
 
 | 
			
		||||
@@ -32,15 +32,18 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type sourceURL struct {
 | 
			
		||||
	url      string
 | 
			
		||||
	nodeName string
 | 
			
		||||
	updates  chan<- interface{}
 | 
			
		||||
	data     []byte
 | 
			
		||||
	url         string
 | 
			
		||||
	header      http.Header
 | 
			
		||||
	nodeName    string
 | 
			
		||||
	updates     chan<- interface{}
 | 
			
		||||
	data        []byte
 | 
			
		||||
	failureLogs int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSourceURL(url, nodeName string, period time.Duration, updates chan<- interface{}) {
 | 
			
		||||
func NewSourceURL(url string, header http.Header, nodeName string, period time.Duration, updates chan<- interface{}) {
 | 
			
		||||
	config := &sourceURL{
 | 
			
		||||
		url:      url,
 | 
			
		||||
		header:   header,
 | 
			
		||||
		nodeName: nodeName,
 | 
			
		||||
		updates:  updates,
 | 
			
		||||
		data:     nil,
 | 
			
		||||
@@ -51,7 +54,19 @@ func NewSourceURL(url, nodeName string, period time.Duration, updates chan<- int
 | 
			
		||||
 | 
			
		||||
func (s *sourceURL) run() {
 | 
			
		||||
	if err := s.extractFromURL(); err != nil {
 | 
			
		||||
		glog.Errorf("Failed to read URL: %v", err)
 | 
			
		||||
		// Don't log this multiple times per minute. The first few entries should be
 | 
			
		||||
		// enough to get the point across.
 | 
			
		||||
		if s.failureLogs < 3 {
 | 
			
		||||
			glog.Warningf("Failed to read pods from URL: %v", err)
 | 
			
		||||
		} else if s.failureLogs == 3 {
 | 
			
		||||
			glog.Warningf("Failed to read pods from URL. Won't log this message anymore: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		s.failureLogs++
 | 
			
		||||
	} else {
 | 
			
		||||
		if s.failureLogs > 0 {
 | 
			
		||||
			glog.Info("Successfully read pods from URL.")
 | 
			
		||||
			s.failureLogs = 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -60,7 +75,13 @@ func (s *sourceURL) applyDefaults(pod *api.Pod) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *sourceURL) extractFromURL() error {
 | 
			
		||||
	resp, err := http.Get(s.url)
 | 
			
		||||
	req, err := http.NewRequest("GET", s.url, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header = s.header
 | 
			
		||||
	client := &http.Client{}
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
@@ -33,7 +34,7 @@ import (
 | 
			
		||||
 | 
			
		||||
func TestURLErrorNotExistNoUpdate(t *testing.T) {
 | 
			
		||||
	ch := make(chan interface{})
 | 
			
		||||
	NewSourceURL("http://localhost:49575/_not_found_", "localhost", time.Millisecond, ch)
 | 
			
		||||
	NewSourceURL("http://localhost:49575/_not_found_", http.Header{}, "localhost", time.Millisecond, ch)
 | 
			
		||||
	select {
 | 
			
		||||
	case got := <-ch:
 | 
			
		||||
		t.Errorf("Expected no update, Got %#v", got)
 | 
			
		||||
@@ -43,7 +44,7 @@ func TestURLErrorNotExistNoUpdate(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestExtractFromHttpBadness(t *testing.T) {
 | 
			
		||||
	ch := make(chan interface{}, 1)
 | 
			
		||||
	c := sourceURL{"http://localhost:49575/_not_found_", "other", ch, nil}
 | 
			
		||||
	c := sourceURL{"http://localhost:49575/_not_found_", http.Header{}, "other", ch, nil, 0}
 | 
			
		||||
	if err := c.extractFromURL(); err == nil {
 | 
			
		||||
		t.Errorf("Expected error")
 | 
			
		||||
	}
 | 
			
		||||
@@ -112,7 +113,7 @@ func TestExtractInvalidPods(t *testing.T) {
 | 
			
		||||
		testServer := httptest.NewServer(&fakeHandler)
 | 
			
		||||
		defer testServer.Close()
 | 
			
		||||
		ch := make(chan interface{}, 1)
 | 
			
		||||
		c := sourceURL{testServer.URL, "localhost", ch, nil}
 | 
			
		||||
		c := sourceURL{testServer.URL, http.Header{}, "localhost", ch, nil, 0}
 | 
			
		||||
		if err := c.extractFromURL(); err == nil {
 | 
			
		||||
			t.Errorf("%s: Expected error", testCase.desc)
 | 
			
		||||
		}
 | 
			
		||||
@@ -259,7 +260,7 @@ func TestExtractPodsFromHTTP(t *testing.T) {
 | 
			
		||||
		testServer := httptest.NewServer(&fakeHandler)
 | 
			
		||||
		defer testServer.Close()
 | 
			
		||||
		ch := make(chan interface{}, 1)
 | 
			
		||||
		c := sourceURL{testServer.URL, hostname, ch, nil}
 | 
			
		||||
		c := sourceURL{testServer.URL, http.Header{}, hostname, ch, nil, 0}
 | 
			
		||||
		if err := c.extractFromURL(); err != nil {
 | 
			
		||||
			t.Errorf("%s: Unexpected error: %v", testCase.desc, err)
 | 
			
		||||
			continue
 | 
			
		||||
@@ -276,3 +277,47 @@ func TestExtractPodsFromHTTP(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestURLWithHeader(t *testing.T) {
 | 
			
		||||
	pod := &api.Pod{
 | 
			
		||||
		TypeMeta: api.TypeMeta{
 | 
			
		||||
			APIVersion: testapi.Version(),
 | 
			
		||||
			Kind:       "Pod",
 | 
			
		||||
		},
 | 
			
		||||
		ObjectMeta: api.ObjectMeta{
 | 
			
		||||
			Name:      "foo",
 | 
			
		||||
			UID:       "111",
 | 
			
		||||
			Namespace: "mynamespace",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: api.PodSpec{
 | 
			
		||||
			NodeName:   "localhost",
 | 
			
		||||
			Containers: []api.Container{{Name: "1", Image: "foo", ImagePullPolicy: api.PullAlways}},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	data, err := json.Marshal(pod)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected json marshalling error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	fakeHandler := util.FakeHandler{
 | 
			
		||||
		StatusCode:   200,
 | 
			
		||||
		ResponseBody: string(data),
 | 
			
		||||
	}
 | 
			
		||||
	testServer := httptest.NewServer(&fakeHandler)
 | 
			
		||||
	defer testServer.Close()
 | 
			
		||||
	ch := make(chan interface{}, 1)
 | 
			
		||||
	header := make(http.Header)
 | 
			
		||||
	header.Set("Metadata-Flavor", "Google")
 | 
			
		||||
	c := sourceURL{testServer.URL, header, "localhost", ch, nil, 0}
 | 
			
		||||
	if err := c.extractFromURL(); err != nil {
 | 
			
		||||
		t.Fatalf("Unexpected error extracting from URL: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	update := (<-ch).(kubelet.PodUpdate)
 | 
			
		||||
 | 
			
		||||
	headerVal := fakeHandler.RequestReceived.Header["Metadata-Flavor"]
 | 
			
		||||
	if len(headerVal) != 1 || headerVal[0] != "Google" {
 | 
			
		||||
		t.Errorf("Header missing expected entry %v. Got %v", header, fakeHandler.RequestReceived.Header)
 | 
			
		||||
	}
 | 
			
		||||
	if len(update.Pods) != 1 {
 | 
			
		||||
		t.Errorf("Received wrong number of pods, expected one: %v", update.Pods)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2322,10 +2322,11 @@ func TestUpdateNewNodeStatus(t *testing.T) {
 | 
			
		||||
	if err := kubelet.updateNodeStatus(); err != nil {
 | 
			
		||||
		t.Errorf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(kubeClient.Actions) != 2 || kubeClient.Actions[1].Action != "update-status-node" {
 | 
			
		||||
		t.Fatalf("unexpected actions: %v", kubeClient.Actions)
 | 
			
		||||
	actions := kubeClient.Actions()
 | 
			
		||||
	if len(actions) != 2 || actions[1].Action != "update-status-node" {
 | 
			
		||||
		t.Fatalf("unexpected actions: %v", actions)
 | 
			
		||||
	}
 | 
			
		||||
	updatedNode, ok := kubeClient.Actions[1].Value.(*api.Node)
 | 
			
		||||
	updatedNode, ok := actions[1].Value.(*api.Node)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Errorf("unexpected object type")
 | 
			
		||||
	}
 | 
			
		||||
@@ -2419,10 +2420,11 @@ func TestUpdateExistingNodeStatus(t *testing.T) {
 | 
			
		||||
	if err := kubelet.updateNodeStatus(); err != nil {
 | 
			
		||||
		t.Errorf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(kubeClient.Actions) != 2 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v", kubeClient.Actions)
 | 
			
		||||
	actions := kubeClient.Actions()
 | 
			
		||||
	if len(actions) != 2 {
 | 
			
		||||
		t.Errorf("unexpected actions: %v", actions)
 | 
			
		||||
	}
 | 
			
		||||
	updatedNode, ok := kubeClient.Actions[1].Value.(*api.Node)
 | 
			
		||||
	updatedNode, ok := actions[1].Value.(*api.Node)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Errorf("unexpected object type")
 | 
			
		||||
	}
 | 
			
		||||
@@ -2506,10 +2508,11 @@ func TestUpdateNodeStatusWithoutContainerRuntime(t *testing.T) {
 | 
			
		||||
	if err := kubelet.updateNodeStatus(); err != nil {
 | 
			
		||||
		t.Errorf("unexpected error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(kubeClient.Actions) != 2 || kubeClient.Actions[1].Action != "update-status-node" {
 | 
			
		||||
		t.Fatalf("unexpected actions: %v", kubeClient.Actions)
 | 
			
		||||
	actions := kubeClient.Actions()
 | 
			
		||||
	if len(actions) != 2 || actions[1].Action != "update-status-node" {
 | 
			
		||||
		t.Fatalf("unexpected actions: %v", actions)
 | 
			
		||||
	}
 | 
			
		||||
	updatedNode, ok := kubeClient.Actions[1].Value.(*api.Node)
 | 
			
		||||
	updatedNode, ok := actions[1].Value.(*api.Node)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		t.Errorf("unexpected object type")
 | 
			
		||||
	}
 | 
			
		||||
@@ -2536,8 +2539,8 @@ func TestUpdateNodeStatusError(t *testing.T) {
 | 
			
		||||
	if err := kubelet.updateNodeStatus(); err == nil {
 | 
			
		||||
		t.Errorf("unexpected non error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(testKubelet.fakeKubeClient.Actions) != nodeStatusUpdateRetry {
 | 
			
		||||
		t.Errorf("unexpected actions: %v", testKubelet.fakeKubeClient.Actions)
 | 
			
		||||
	if len(testKubelet.fakeKubeClient.Actions()) != nodeStatusUpdateRetry {
 | 
			
		||||
		t.Errorf("unexpected actions: %v", testKubelet.fakeKubeClient.Actions())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user