<commit_before>// package boltbk implements BoltDB backed backend for standalone instances
package boltbk

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/gravitational/teleport/backend"

	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/boltdb/bolt"
)

type BoltBackend struct {
	sync.Mutex

	db    *bolt.DB
	locks map[string]time.Time
}

func New(path string) (*BoltBackend, error) {
	db, err := bolt.Open(path, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltBackend{
		db:    db,
		locks: make(map[string]time.Time),
	}, nil
}

func (b *BoltBackend) AcquireLock(token string, ttl time.Duration) error {
	b.Lock()
	defer b.Unlock()

	expires, ok := b.locks[token]
	if ok && expires.After(time.Now()) {
		return &backend.AlreadyExistsError{
			Message: fmt.Sprintf("lock %v already locked", token)}
	}
	b.locks[token] = time.Now().Add(ttl)
	return nil
}

func (b *BoltBackend) ReleaseLock(token string) error {
	b.Lock()
	defer b.Unlock()

	expires, ok := b.locks[token]
	if !ok || expires.Before(time.Now()) {
		return &backend.NotFoundError{
			Message: fmt.Sprintf(
				"lock %v is deleted or expired", token),
		}
	}
	delete(b.locks, token)
	return nil
}

func (b *BoltBackend) Close() error {
	return nil
}

// GetUsers  returns a list of users registered in the backend
func (b *BoltBackend) GetUsers() ([]string, error) {
	out := []string{}
	err := b.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("users"))
		if b == nil {
			return nil
		}
		c := b.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			out = append(out, string(k))
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

// DeleteUser deletes a user with all the keys from the backend
func (b *BoltBackend) DeleteUser(user string) error {
	return b.deleteBucket([]string{"users"}, user)
}

func (b *BoltBackend) UpsertUserCA(a backend.CA) error {
	return b.upsertJSONKey([]string{"userca"}, "val", a)
}

func (b *BoltBackend) GetUserCA() (*backend.CA, error) {
	var ca *backend.CA
	return ca, b.getJSONKey([]string{"userca"}, "val", &ca)
}

func (b *BoltBackend) GetUserCAPub() ([]byte, error) {
	ca, err := b.GetUserCA()
	if err != nil {
		return nil, err
	}
	return ca.Pub, nil
}

func (b *BoltBackend) UpsertHostCA(a backend.CA) error {
	return b.upsertJSONKey([]string{"hostca"}, "val", a)
}

func (b *BoltBackend) GetHostCA() (*backend.CA, error) {
	var ca *backend.CA
	return ca, b.getJSONKey([]string{"hostca"}, "val", &ca)
}

func (b *BoltBackend) GetHostCAPub() ([]byte, error) {
	ca, err := b.GetHostCA()
	if err != nil {
		return nil, err
	}
	return ca.Pub, nil
}

func (b *BoltBackend) GetUserKeys(user string) ([]backend.AuthorizedKey, error) {
	if user == "" {
		return nil, &backend.MissingParameterError{Param: "user"}
	}
	values := []backend.AuthorizedKey{}
	err := b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, []string{"users", user, "keys"})
		if err != nil {
			return err
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var key *backend.AuthorizedKey
			if err := json.Unmarshal(v, &key); err != nil {
				return err
			}
			values = append(values, *key)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (b *BoltBackend) UpsertToken(token, fqdn string, ttl time.Duration) error {
	return b.upsertKey([]string{"tokens"}, token, []byte(fqdn))
}

func (b *BoltBackend) GetToken(token string) (string, error) {
	var fqdn []byte
	if err := b.getKey([]string{"tokens"}, token, &fqdn); err != nil {
		return "", err
	}
	return string(fqdn), nil
}

func (b *BoltBackend) DeleteToken(token string) error {
	return b.deleteKey([]string{"tokens"}, token)
}

func (b *BoltBackend) UpsertUserKey(user string, key backend.AuthorizedKey, ttl time.Duration) error {
	if user == "" {
		return &backend.MissingParameterError{Param: "user"}
	}
	if key.ID == "" {
		return &backend.MissingParameterError{Param: "key.id"}
	}
	if len(key.Value) == 0 {
		return &backend.MissingParameterError{Param: "key.val"}
	}
	return b.upsertJSONKey([]string{"users", user, "keys"}, key.ID, key)
}

func (b *BoltBackend) DeleteUserKey(user, keyID string) error {
	if user == "" {
		return &backend.MissingParameterError{Param: "user"}
	}
	if keyID == "" {
		return &backend.MissingParameterError{Param: "key.id"}
	}
	return b.deleteKey([]string{"users", user, "keys"}, keyID)
}

func (b *BoltBackend) UpsertServer(s backend.Server, ttl time.Duration) error {
	return b.upsertJSONKey([]string{"servers"}, "val", s)
}

func (b *BoltBackend) GetServers() ([]backend.Server, error) {
	values := []backend.Server{}
	err := b.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("servers"))
		if b == nil {
			return nil
		}
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var key *backend.Server
			if err := json.Unmarshal(v, &key); err != nil {
				return err
			}
			values = append(values, *key)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (b *BoltBackend) UpsertPasswordHash(user string, hash []byte) error {
	return b.upsertKey([]string{"users", user}, "password-hash", hash)
}

func (b *BoltBackend) GetPasswordHash(user string) ([]byte, error) {
	var hash []byte
	err := b.getKey([]string{"users", user}, "password-hash", &hash)
	if err != nil {
		return nil, err
	}
	return hash, nil
}

func (b *BoltBackend) UpsertWebSession(user, sid string, s backend.WebSession, ttl time.Duration) error {
	return b.upsertJSONKey([]string{"users", user, "web-sessions"}, sid, s)
}

func (b *BoltBackend) GetWebSession(user, sid string) (*backend.WebSession, error) {
	var ws *backend.WebSession
	return ws, b.getJSONKey([]string{"users", user, "web-sessions"}, sid, &ws)
}

func (b *BoltBackend) GetWebSessionsKeys(user string) ([]backend.AuthorizedKey, error) {
	values := []backend.AuthorizedKey{}
	err := b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, []string{"users", user, "web-sessions"})
		if err != nil {
			return err
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var ws *backend.WebSession
			if err := json.Unmarshal(v, &ws); err != nil {
				return err
			}
			values = append(values, backend.AuthorizedKey{
				ID:    string(k),
				Value: ws.Pub,
			})
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (b *BoltBackend) DeleteWebSession(user, sid string) error {
	return b.deleteKey([]string{"users", user, "web-sessions"}, sid)
}

func (b *BoltBackend) UpsertWebTun(t backend.WebTun, ttl time.Duration) error {
	if t.Prefix == "" {
		return &backend.MissingParameterError{Param: "Prefix"}
	}
	return b.upsertJSONKey([]string{"web-tuns"}, t.Prefix, t)
}

func (b *BoltBackend) GetWebTun(prefix string) (*backend.WebTun, error) {
	var wt *backend.WebTun
	return wt, b.getJSONKey([]string{"web-tuns"}, prefix, &wt)
}

func (b *BoltBackend) DeleteWebTun(prefix string) error {
	return b.deleteKey([]string{"web-tuns"}, prefix)
}

func (b *BoltBackend) GetWebTuns() ([]backend.WebTun, error) {
	out := []backend.WebTun{}
	err := b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, []string{"web-tuns"})
		if err != nil {
			return err
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var wt *backend.WebTun
			if err := json.Unmarshal(v, &wt); err != nil {
				return err
			}
			out = append(out, *wt)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (b *BoltBackend) deleteBucket(buckets []string, bucket string) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		if bkt.Bucket([]byte(bucket)) == nil {
			return &backend.NotFoundError{
				fmt.Sprintf("%v not found", bucket)}
		}
		return bkt.DeleteBucket([]byte(bucket))
	})
}

func (b *BoltBackend) deleteKey(buckets []string, key string) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		if bkt.Get([]byte(key)) == nil {
			return &backend.NotFoundError{}
		}
		return bkt.Delete([]byte(key))
	})
}

func (b *BoltBackend) upsertKey(buckets []string, key string, bytes []byte) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := upsertBucket(tx, buckets)
		if err != nil {
			return err
		}
		return bkt.Put([]byte(key), bytes)
	})
}

func (b *BoltBackend) upsertJSONKey(buckets []string, key string, val interface{}) error {
	bytes, err := json.Marshal(val)
	if err != nil {
		return err
	}
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := upsertBucket(tx, buckets)
		if err != nil {
			return err
		}
		return bkt.Put([]byte(key), bytes)
	})
}

func (b *BoltBackend) getJSONKey(buckets []string, key string, val interface{}) error {
	return b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		bytes := bkt.Get([]byte(key))
		if bytes == nil {
			return &backend.NotFoundError{
				Message: fmt.Sprintf("%v %v not found", buckets, key),
			}
		}
		return json.Unmarshal(bytes, val)
	})
}

func (b *BoltBackend) getKey(buckets []string, key string, val *[]byte) error {
	return b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		bytes := bkt.Get([]byte(key))
		if bytes == nil {
			return &backend.NotFoundError{
				Message: fmt.Sprintf("%v %v not found", buckets, key),
			}
		}
		*val = bytes
		return nil
	})
}

func upsertBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
	bkt, err := b.CreateBucketIfNotExists([]byte(buckets[0]))
	if err != nil {
		return nil, err
	}
	for _, key := range buckets[1:] {
		bkt, err = bkt.CreateBucketIfNotExists([]byte(key))
		if err != nil {
			return nil, err
		}
	}
	return bkt, nil
}

func getBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
	bkt := b.Bucket([]byte(buckets[0]))
	if bkt == nil {
		return nil, &backend.NotFoundError{
			Message: fmt.Sprintf("bucket %v not found", buckets[0])}
	}
	for _, key := range buckets[1:] {
		bkt = bkt.Bucket([]byte(key))
		if bkt == nil {
			return nil, &backend.NotFoundError{
				Message: fmt.Sprintf("bucket %v not found", key)}
		}
	}
	return bkt, nil
}
<commit_msg>quick fix<commit_after>// package boltbk implements BoltDB backed backend for standalone instances
package boltbk

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/gravitational/teleport/backend"

	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/boltdb/bolt"
)

type BoltBackend struct {
	sync.Mutex

	db    *bolt.DB
	locks map[string]time.Time
}

func New(path string) (*BoltBackend, error) {
	db, err := bolt.Open(path, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltBackend{
		db:    db,
		locks: make(map[string]time.Time),
	}, nil
}

func (b *BoltBackend) AcquireLock(token string, ttl time.Duration) error {
	b.Lock()
	defer b.Unlock()

	expires, ok := b.locks[token]
	if ok && expires.After(time.Now()) {
		return &backend.AlreadyExistsError{
			Message: fmt.Sprintf("lock %v already locked", token)}
	}
	b.locks[token] = time.Now().Add(ttl)
	return nil
}

func (b *BoltBackend) ReleaseLock(token string) error {
	b.Lock()
	defer b.Unlock()

	expires, ok := b.locks[token]
	if !ok || expires.Before(time.Now()) {
		return &backend.NotFoundError{
			Message: fmt.Sprintf(
				"lock %v is deleted or expired", token),
		}
	}
	delete(b.locks, token)
	return nil
}

func (b *BoltBackend) Close() error {
	return nil
}

// GetUsers  returns a list of users registered in the backend
func (b *BoltBackend) GetUsers() ([]string, error) {
	out := []string{}
	err := b.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("users"))
		if b == nil {
			return nil
		}
		c := b.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			out = append(out, string(k))
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

// DeleteUser deletes a user with all the keys from the backend
func (b *BoltBackend) DeleteUser(user string) error {
	return b.deleteBucket([]string{"users"}, user)
}

func (b *BoltBackend) UpsertUserCA(a backend.CA) error {
	return b.upsertJSONKey([]string{"userca"}, "val", a)
}

func (b *BoltBackend) GetUserCA() (*backend.CA, error) {
	var ca *backend.CA
	return ca, b.getJSONKey([]string{"userca"}, "val", &ca)
}

func (b *BoltBackend) GetUserCAPub() ([]byte, error) {
	ca, err := b.GetUserCA()
	if err != nil {
		return nil, err
	}
	return ca.Pub, nil
}

func (b *BoltBackend) UpsertHostCA(a backend.CA) error {
	return b.upsertJSONKey([]string{"hostca"}, "val", a)
}

func (b *BoltBackend) GetHostCA() (*backend.CA, error) {
	var ca *backend.CA
	return ca, b.getJSONKey([]string{"hostca"}, "val", &ca)
}

func (b *BoltBackend) GetHostCAPub() ([]byte, error) {
	ca, err := b.GetHostCA()
	if err != nil {
		return nil, err
	}
	return ca.Pub, nil
}

func (b *BoltBackend) GetUserKeys(user string) ([]backend.AuthorizedKey, error) {
	if user == "" {
		return nil, &backend.MissingParameterError{Param: "user"}
	}
	values := []backend.AuthorizedKey{}
	err := b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, []string{"users", user, "keys"})
		if err != nil {
			if _, ok := err.(*backend.NotFoundError); ok {
				return nil
			}
			return err
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var key *backend.AuthorizedKey
			if err := json.Unmarshal(v, &key); err != nil {
				return err
			}
			values = append(values, *key)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (b *BoltBackend) UpsertToken(token, fqdn string, ttl time.Duration) error {
	return b.upsertKey([]string{"tokens"}, token, []byte(fqdn))
}

func (b *BoltBackend) GetToken(token string) (string, error) {
	var fqdn []byte
	if err := b.getKey([]string{"tokens"}, token, &fqdn); err != nil {
		return "", err
	}
	return string(fqdn), nil
}

func (b *BoltBackend) DeleteToken(token string) error {
	return b.deleteKey([]string{"tokens"}, token)
}

func (b *BoltBackend) UpsertUserKey(user string, key backend.AuthorizedKey, ttl time.Duration) error {
	if user == "" {
		return &backend.MissingParameterError{Param: "user"}
	}
	if key.ID == "" {
		return &backend.MissingParameterError{Param: "key.id"}
	}
	if len(key.Value) == 0 {
		return &backend.MissingParameterError{Param: "key.val"}
	}
	return b.upsertJSONKey([]string{"users", user, "keys"}, key.ID, key)
}

func (b *BoltBackend) DeleteUserKey(user, keyID string) error {
	if user == "" {
		return &backend.MissingParameterError{Param: "user"}
	}
	if keyID == "" {
		return &backend.MissingParameterError{Param: "key.id"}
	}
	return b.deleteKey([]string{"users", user, "keys"}, keyID)
}

func (b *BoltBackend) UpsertServer(s backend.Server, ttl time.Duration) error {
	return b.upsertJSONKey([]string{"servers"}, "val", s)
}

func (b *BoltBackend) GetServers() ([]backend.Server, error) {
	values := []backend.Server{}
	err := b.db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("servers"))
		if b == nil {
			return nil
		}
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var key *backend.Server
			if err := json.Unmarshal(v, &key); err != nil {
				return err
			}
			values = append(values, *key)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (b *BoltBackend) UpsertPasswordHash(user string, hash []byte) error {
	return b.upsertKey([]string{"users", user}, "password-hash", hash)
}

func (b *BoltBackend) GetPasswordHash(user string) ([]byte, error) {
	var hash []byte
	err := b.getKey([]string{"users", user}, "password-hash", &hash)
	if err != nil {
		return nil, err
	}
	return hash, nil
}

func (b *BoltBackend) UpsertWebSession(user, sid string, s backend.WebSession, ttl time.Duration) error {
	return b.upsertJSONKey([]string{"users", user, "web-sessions"}, sid, s)
}

func (b *BoltBackend) GetWebSession(user, sid string) (*backend.WebSession, error) {
	var ws *backend.WebSession
	return ws, b.getJSONKey([]string{"users", user, "web-sessions"}, sid, &ws)
}

func (b *BoltBackend) GetWebSessionsKeys(user string) ([]backend.AuthorizedKey, error) {
	values := []backend.AuthorizedKey{}
	err := b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, []string{"users", user, "web-sessions"})
		if err != nil {
			if _, ok := err.(*backend.NotFoundError); ok {
				return nil
			}
			return err
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var ws *backend.WebSession
			if err := json.Unmarshal(v, &ws); err != nil {
				return err
			}
			values = append(values, backend.AuthorizedKey{
				ID:    string(k),
				Value: ws.Pub,
			})
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return values, nil
}

func (b *BoltBackend) DeleteWebSession(user, sid string) error {
	return b.deleteKey([]string{"users", user, "web-sessions"}, sid)
}

func (b *BoltBackend) UpsertWebTun(t backend.WebTun, ttl time.Duration) error {
	if t.Prefix == "" {
		return &backend.MissingParameterError{Param: "Prefix"}
	}
	return b.upsertJSONKey([]string{"web-tuns"}, t.Prefix, t)
}

func (b *BoltBackend) GetWebTun(prefix string) (*backend.WebTun, error) {
	var wt *backend.WebTun
	return wt, b.getJSONKey([]string{"web-tuns"}, prefix, &wt)
}

func (b *BoltBackend) DeleteWebTun(prefix string) error {
	return b.deleteKey([]string{"web-tuns"}, prefix)
}

func (b *BoltBackend) GetWebTuns() ([]backend.WebTun, error) {
	out := []backend.WebTun{}
	err := b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, []string{"web-tuns"})
		if err != nil {
			return err
		}
		c := bkt.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			var wt *backend.WebTun
			if err := json.Unmarshal(v, &wt); err != nil {
				return err
			}
			out = append(out, *wt)
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return out, nil
}

func (b *BoltBackend) deleteBucket(buckets []string, bucket string) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		if bkt.Bucket([]byte(bucket)) == nil {
			return &backend.NotFoundError{
				fmt.Sprintf("%v not found", bucket)}
		}
		return bkt.DeleteBucket([]byte(bucket))
	})
}

func (b *BoltBackend) deleteKey(buckets []string, key string) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		if bkt.Get([]byte(key)) == nil {
			return &backend.NotFoundError{}
		}
		return bkt.Delete([]byte(key))
	})
}

func (b *BoltBackend) upsertKey(buckets []string, key string, bytes []byte) error {
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := upsertBucket(tx, buckets)
		if err != nil {
			return err
		}
		return bkt.Put([]byte(key), bytes)
	})
}

func (b *BoltBackend) upsertJSONKey(buckets []string, key string, val interface{}) error {
	bytes, err := json.Marshal(val)
	if err != nil {
		return err
	}
	return b.db.Update(func(tx *bolt.Tx) error {
		bkt, err := upsertBucket(tx, buckets)
		if err != nil {
			return err
		}
		return bkt.Put([]byte(key), bytes)
	})
}

func (b *BoltBackend) getJSONKey(buckets []string, key string, val interface{}) error {
	return b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		bytes := bkt.Get([]byte(key))
		if bytes == nil {
			return &backend.NotFoundError{
				Message: fmt.Sprintf("%v %v not found", buckets, key),
			}
		}
		return json.Unmarshal(bytes, val)
	})
}

func (b *BoltBackend) getKey(buckets []string, key string, val *[]byte) error {
	return b.db.View(func(tx *bolt.Tx) error {
		bkt, err := getBucket(tx, buckets)
		if err != nil {
			return err
		}
		bytes := bkt.Get([]byte(key))
		if bytes == nil {
			return &backend.NotFoundError{
				Message: fmt.Sprintf("%v %v not found", buckets, key),
			}
		}
		*val = bytes
		return nil
	})
}

func upsertBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
	bkt, err := b.CreateBucketIfNotExists([]byte(buckets[0]))
	if err != nil {
		return nil, err
	}
	for _, key := range buckets[1:] {
		bkt, err = bkt.CreateBucketIfNotExists([]byte(key))
		if err != nil {
			return nil, err
		}
	}
	return bkt, nil
}

func getBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
	bkt := b.Bucket([]byte(buckets[0]))
	if bkt == nil {
		return nil, &backend.NotFoundError{
			Message: fmt.Sprintf("bucket %v not found", buckets[0])}
	}
	for _, key := range buckets[1:] {
		bkt = bkt.Bucket([]byte(key))
		if bkt == nil {
			return nil, &backend.NotFoundError{
				Message: fmt.Sprintf("bucket %v not found", key)}
		}
	}
	return bkt, nil
}
<|endoftext|>


<commit_before>package protocol_test

import (
	"github.com/lucas-clemente/quic-go/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("minmax", func() {
	It("calculates packet number max", func() {
		Expect(protocol.MaxPacketNumber(1, 2)).To(Equal(protocol.PacketNumber(2)))
	})

	It("calculates packet number min", func() {
		Expect(protocol.MinPacketNumber(1, 2)).To(Equal(protocol.PacketNumber(1)))
	})
})
<commit_msg>add protocol tests<commit_after>package protocol_test

import (
	"github.com/lucas-clemente/quic-go/protocol"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("minmax", func() {
	It("calculates packet number max", func() {
		Expect(protocol.MaxPacketNumber(1, 2)).To(Equal(protocol.PacketNumber(2)))
		Expect(protocol.MaxPacketNumber(2, 1)).To(Equal(protocol.PacketNumber(2)))
	})

	It("calculates packet number min", func() {
		Expect(protocol.MinPacketNumber(1, 2)).To(Equal(protocol.PacketNumber(1)))
		Expect(protocol.MinPacketNumber(2, 1)).To(Equal(protocol.PacketNumber(1)))
	})
})
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package flagutil

import (
	"flag"
	"fmt"
	"net/url"

	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/config/secret"
	"k8s.io/test-infra/prow/git"
	"k8s.io/test-infra/prow/github"
)

// GitHubOptions holds options for interacting with GitHub.
type GitHubOptions struct {
	endpoint            Strings
	graphqlEndpoint     string
	TokenPath           string
	deprecatedTokenFile string
}

// AddFlags injects GitHub options into the given FlagSet.
func (o *GitHubOptions) AddFlags(fs *flag.FlagSet) {
	o.addFlags(true, fs)
}

// AddFlagsWithoutDefaultGitHubTokenPath injects GitHub options into the given
// Flagset without setting a default for for the githubTokenPath, allowing to
// use an anonymous GitHub client
func (o *GitHubOptions) AddFlagsWithoutDefaultGitHubTokenPath(fs *flag.FlagSet) {
	o.addFlags(false, fs)
}

func (o *GitHubOptions) addFlags(wantDefaultGitHubTokenPath bool, fs *flag.FlagSet) {
	o.endpoint = NewStrings(github.DefaultAPIEndpoint)
	fs.Var(&o.endpoint, "github-endpoint", "GitHub's API endpoint (may differ for enterprise).")
	fs.StringVar(&o.graphqlEndpoint, "github-graphql-endpoint", github.DefaultGraphQLEndpoint, "GitHub GraphQL API endpoint (may differ for enterprise).")
	defaultGitHubTokenPath := ""
	if wantDefaultGitHubTokenPath {
		defaultGitHubTokenPath = "/etc/github/oauth"
	}
	fs.StringVar(&o.TokenPath, "github-token-path", defaultGitHubTokenPath, "Path to the file containing the GitHub OAuth secret.")
	fs.StringVar(&o.deprecatedTokenFile, "github-token-file", "", "DEPRECATED: use -github-token-path instead.  -github-token-file may be removed anytime after 2019-01-01.")
}

// Validate validates GitHub options.
func (o *GitHubOptions) Validate(dryRun bool) error {
	for _, uri := range o.endpoint.Strings() {
		if _, err := url.ParseRequestURI(uri); err != nil {
			return fmt.Errorf("invalid -github-endpoint URI: %q", uri)
		}
	}

	if _, err := url.Parse(o.graphqlEndpoint); err != nil {
		return fmt.Errorf("invalid -github-graphql-endpoint URI: %q", o.graphqlEndpoint)
	}

	if o.deprecatedTokenFile != "" {
		o.TokenPath = o.deprecatedTokenFile
		logrus.Error("-github-token-file is deprecated and may be removed anytime after 2019-01-01.  Use -github-token-path instead.")
	}

	if o.TokenPath == "" {
		logrus.Warn("empty -github-token-path, will use anonymous github client")
	}

	return nil
}

// GitHubClientWithLogFields returns a GitHub client with extra logging fields
func (o *GitHubOptions) GitHubClientWithLogFields(secretAgent *secret.Agent, dryRun bool, fields logrus.Fields) (client *github.Client, err error) {
	var generator *func() []byte
	if o.TokenPath == "" {
		generatorFunc := func() []byte {
			return []byte{}
		}
		generator = &generatorFunc
	} else {
		if secretAgent == nil {
			return nil, fmt.Errorf("cannot store token from %q without a secret agent", o.TokenPath)
		}
		generatorFunc := secretAgent.GetTokenGenerator(o.TokenPath)
		generator = &generatorFunc
	}

	if dryRun {
		return github.NewDryRunClientWithFields(fields, *generator, o.graphqlEndpoint, o.endpoint.Strings()...), nil
	}
	return github.NewClientWithFields(fields, *generator, o.graphqlEndpoint, o.endpoint.Strings()...), nil
}

// GitHubClient returns a GitHub client.
func (o *GitHubOptions) GitHubClient(secretAgent *secret.Agent, dryRun bool) (client *github.Client, err error) {
	return o.GitHubClientWithLogFields(secretAgent, dryRun, logrus.Fields{})
}

// GitClient returns a Git client.
func (o *GitHubOptions) GitClient(secretAgent *secret.Agent, dryRun bool) (client *git.Client, err error) {
	client, err = git.NewClient()
	if err != nil {
		return nil, err
	}

	// We must capture the value of client here to prevent issues related
	// to the use of named return values when an error is encountered.
	// Without this, we risk a nil pointer dereference.
	defer func(client *git.Client) {
		if err != nil {
			client.Clean()
		}
	}(client)

	// Get the bot's name in order to set credentials for the Git client.
	githubClient, err := o.GitHubClient(secretAgent, dryRun)
	if err != nil {
		return nil, fmt.Errorf("error getting GitHub client: %v", err)
	}
	botName, err := githubClient.BotName()
	if err != nil {
		return nil, fmt.Errorf("error getting bot name: %v", err)
	}
	client.SetCredentials(botName, secretAgent.GetTokenGenerator(o.TokenPath))

	return client, nil
}
<commit_msg>Addressed Steve's Comments on Flag Validation<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package flagutil

import (
	"flag"
	"fmt"
	"net/url"

	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/config/secret"
	"k8s.io/test-infra/prow/git"
	"k8s.io/test-infra/prow/github"
)

// GitHubOptions holds options for interacting with GitHub.
type GitHubOptions struct {
	endpoint            Strings
	graphqlEndpoint     string
	TokenPath           string
	deprecatedTokenFile string
}

// AddFlags injects GitHub options into the given FlagSet.
func (o *GitHubOptions) AddFlags(fs *flag.FlagSet) {
	o.addFlags(true, fs)
}

// AddFlagsWithoutDefaultGitHubTokenPath injects GitHub options into the given
// Flagset without setting a default for for the githubTokenPath, allowing to
// use an anonymous GitHub client
func (o *GitHubOptions) AddFlagsWithoutDefaultGitHubTokenPath(fs *flag.FlagSet) {
	o.addFlags(false, fs)
}

func (o *GitHubOptions) addFlags(wantDefaultGitHubTokenPath bool, fs *flag.FlagSet) {
	o.endpoint = NewStrings(github.DefaultAPIEndpoint)
	fs.Var(&o.endpoint, "github-endpoint", "GitHub's API endpoint (may differ for enterprise).")
	fs.StringVar(&o.graphqlEndpoint, "github-graphql-endpoint", github.DefaultGraphQLEndpoint, "GitHub GraphQL API endpoint (may differ for enterprise).")
	defaultGitHubTokenPath := ""
	if wantDefaultGitHubTokenPath {
		defaultGitHubTokenPath = "/etc/github/oauth"
	}
	fs.StringVar(&o.TokenPath, "github-token-path", defaultGitHubTokenPath, "Path to the file containing the GitHub OAuth secret.")
	fs.StringVar(&o.deprecatedTokenFile, "github-token-file", "", "DEPRECATED: use -github-token-path instead.  -github-token-file may be removed anytime after 2019-01-01.")
}

// Validate validates GitHub options.
func (o *GitHubOptions) Validate(dryRun bool) error {
	for _, uri := range o.endpoint.Strings() {
		if uri == "" {
			uri = github.DefaultAPIEndpoint
		} else if _, err := url.ParseRequestURI(uri); err != nil {
			return fmt.Errorf("invalid -github-endpoint URI: %q", uri)
		}
	}

	if o.graphqlEndpoint == "" {
		o.graphqlEndpoint = github.DefaultGraphQLEndpoint
	} else if _, err := url.Parse(o.graphqlEndpoint); err != nil {
		return fmt.Errorf("invalid -github-graphql-endpoint URI: %q", o.graphqlEndpoint)
	}

	if o.deprecatedTokenFile != "" {
		o.TokenPath = o.deprecatedTokenFile
		logrus.Error("-github-token-file is deprecated and may be removed anytime after 2019-01-01.  Use -github-token-path instead.")
	}

	if o.TokenPath == "" {
		logrus.Warn("empty -github-token-path, will use anonymous github client")
	}

	return nil
}

// GitHubClientWithLogFields returns a GitHub client with extra logging fields
func (o *GitHubOptions) GitHubClientWithLogFields(secretAgent *secret.Agent, dryRun bool, fields logrus.Fields) (client *github.Client, err error) {
	var generator *func() []byte
	if o.TokenPath == "" {
		generatorFunc := func() []byte {
			return []byte{}
		}
		generator = &generatorFunc
	} else {
		if secretAgent == nil {
			return nil, fmt.Errorf("cannot store token from %q without a secret agent", o.TokenPath)
		}
		generatorFunc := secretAgent.GetTokenGenerator(o.TokenPath)
		generator = &generatorFunc
	}

	if dryRun {
		return github.NewDryRunClientWithFields(fields, *generator, o.graphqlEndpoint, o.endpoint.Strings()...), nil
	}
	return github.NewClientWithFields(fields, *generator, o.graphqlEndpoint, o.endpoint.Strings()...), nil
}

// GitHubClient returns a GitHub client.
func (o *GitHubOptions) GitHubClient(secretAgent *secret.Agent, dryRun bool) (client *github.Client, err error) {
	return o.GitHubClientWithLogFields(secretAgent, dryRun, logrus.Fields{})
}

// GitClient returns a Git client.
func (o *GitHubOptions) GitClient(secretAgent *secret.Agent, dryRun bool) (client *git.Client, err error) {
	client, err = git.NewClient()
	if err != nil {
		return nil, err
	}

	// We must capture the value of client here to prevent issues related
	// to the use of named return values when an error is encountered.
	// Without this, we risk a nil pointer dereference.
	defer func(client *git.Client) {
		if err != nil {
			client.Clean()
		}
	}(client)

	// Get the bot's name in order to set credentials for the Git client.
	githubClient, err := o.GitHubClient(secretAgent, dryRun)
	if err != nil {
		return nil, fmt.Errorf("error getting GitHub client: %v", err)
	}
	botName, err := githubClient.BotName()
	if err != nil {
		return nil, fmt.Errorf("error getting bot name: %v", err)
	}
	client.SetCredentials(botName, secretAgent.GetTokenGenerator(o.TokenPath))

	return client, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

// Package cat adds cat images to an issue or PR in response to a /meow comment
package cat

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

	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

var (
	match = regexp.MustCompile(`(?mi)^/meow(vie)?(?: (.+))?\s*$`)
	meow  = &realClowder{
		url: "https://api.thecatapi.com/v1/images/search?format=json&results_per_page=1",
	}
)

const (
	pluginName = "cat"
	grumpyURL  = "https://upload.wikimedia.org/wikipedia/commons/e/ee/Grumpy_Cat_by_Gage_Skidmore.jpg"
)

func init() {
	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
}

func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
	pluginHelp := &pluginhelp.PluginHelp{
		Description: "The cat plugin adds a cat image to an issue or PR in response to the `/meow` command.",
		Config: map[string]string{
			"": fmt.Sprintf("The cat plugin uses an api key for thecatapi.com stored in %s.", config.Cat.KeyPath),
		},
	}
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/meow(vie) [CATegory]",
		Description: "Add a cat image to the issue or PR",
		Featured:    false,
		WhoCanUse:   "Anyone",
		Examples:    []string{"/meow", "/meow caturday", "/meowvie clothes"},
	})
	return pluginHelp, nil
}

type githubClient interface {
	CreateComment(owner, repo string, number int, comment string) error
}

type clowder interface {
	readCat(string, bool) (string, error)
}

type realClowder struct {
	url     string
	lock    sync.RWMutex
	update  time.Time
	key     string
	keyPath string
}

func (c *realClowder) setKey(keyPath string, log *logrus.Entry) {
	c.lock.Lock()
	defer c.lock.Unlock()
	if !time.Now().After(c.update) {
		return
	}
	c.update = time.Now().Add(1 * time.Minute)
	if keyPath == "" {
		c.key = ""
		return
	}
	b, err := ioutil.ReadFile(keyPath)
	if err == nil {
		c.key = strings.TrimSpace(string(b))
		return
	}
	log.WithError(err).Errorf("failed to read key at %s", keyPath)
	c.key = ""
}

type catResult struct {
	Image string `json:"url"`
}

func (cr catResult) Format() (string, error) {
	if cr.Image == "" {
		return "", errors.New("empty image url")
	}
	img, err := url.Parse(cr.Image)
	if err != nil {
		return "", fmt.Errorf("invalid image url %s: %v", cr.Image, err)
	}

	return fmt.Sprintf("![cat image](%s)", img), nil
}

func (c *realClowder) URL(category string, movieCat bool) string {
	c.lock.RLock()
	defer c.lock.RUnlock()
	uri := string(c.url)
	if category != "" {
		uri += "&category=" + url.QueryEscape(category)
	}
	if c.key != "" {
		uri += "&api_key=" + url.QueryEscape(c.key)
	}
	if movieCat {
		uri += "&mime_types=gif"
	}
	return uri
}

func (c *realClowder) readCat(category string, movieCat bool) (string, error) {
	cats := make([]catResult, 0)
	uri := c.URL(category, movieCat)
	if strings.TrimSpace(category) == "no" {
		cats = append(cats, catResult{grumpyURL})
	} else {
		resp, err := http.Get(uri)
		if err != nil {
			return "", fmt.Errorf("could not read cat from %s: %v", uri, err)
		}
		defer resp.Body.Close()
		if sc := resp.StatusCode; sc > 299 || sc < 200 {
			return "", fmt.Errorf("failing %d response from %s", sc, uri)
		}
		if err = json.NewDecoder(resp.Body).Decode(&cats); err != nil {
			return "", err
		}
		if len(cats) < 1 {
			return "", fmt.Errorf("no cats in response from %s", uri)
		}
	}
	a := cats[0]
	if a.Image == "" {
		return "", fmt.Errorf("no image url in response from %s", uri)
	}
	// checking size, GitHub doesn't support big images
	toobig, err := github.ImageTooBig(a.Image)
	if err != nil {
		return "", fmt.Errorf("could not validate image size %s: %v", a.Image, err)
	} else if toobig {
		return "", fmt.Errorf("longcat is too long: %s", a.Image)
	}
	return a.Format()
}

func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
	return handle(
		pc.GitHubClient,
		pc.Logger,
		&e,
		meow,
		func() { meow.setKey(pc.PluginConfig.Cat.KeyPath, pc.Logger) },
	)
}

func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, c clowder, setKey func()) error {
	// Only consider new comments.
	if e.Action != github.GenericCommentActionCreated {
		return nil
	}
	// Make sure they are requesting a cat
	mat := match.FindStringSubmatch(e.Body)
	if mat == nil {
		return nil
	}

	category, movieCat, err := parseMatch(mat)
	if err != nil {
		return err
	}

	// Now that we know this is a relevant event we can set the key.
	setKey()

	org := e.Repo.Owner.Login
	repo := e.Repo.Name
	number := e.Number

	for i := 0; i < 3; i++ {
		resp, err := c.readCat(category, movieCat)
		if err != nil {
			log.WithError(err).Error("Failed to get cat img")
			continue
		}
		return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp))
	}

	var msg string
	if category != "" {
		msg = "Bad category. Please see https://api.thecatapi.com/api/categories/list"
	} else {
		msg = "https://thecatapi.com appears to be down"
	}
	if err := gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil {
		log.WithError(err).Error("Failed to leave comment")
	}

	return errors.New("could not find a valid cat image")
}

func parseMatch(mat []string) (string, bool, error) {
	if len(mat) != 3 {
		err := fmt.Errorf("expected 3 capture groups in regexp match, but got %d", len(mat))
		return "", false, err
	}
	category := strings.TrimSpace(mat[2])
	movieCat := len(mat[1]) > 0 // "vie" suffix is present.
	return category, movieCat, nil
}
<commit_msg>change keyword to grumpy<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

// Package cat adds cat images to an issue or PR in response to a /meow comment
package cat

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

	"github.com/sirupsen/logrus"

	"k8s.io/test-infra/prow/github"
	"k8s.io/test-infra/prow/pluginhelp"
	"k8s.io/test-infra/prow/plugins"
)

var (
	match = regexp.MustCompile(`(?mi)^/meow(vie)?(?: (.+))?\s*$`)
	meow  = &realClowder{
		url: "https://api.thecatapi.com/v1/images/search?format=json&results_per_page=1",
	}
)

const (
	pluginName = "cat"
	grumpyURL  = "https://upload.wikimedia.org/wikipedia/commons/e/ee/Grumpy_Cat_by_Gage_Skidmore.jpg"
)

func init() {
	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
}

func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
	pluginHelp := &pluginhelp.PluginHelp{
		Description: "The cat plugin adds a cat image to an issue or PR in response to the `/meow` command.",
		Config: map[string]string{
			"": fmt.Sprintf("The cat plugin uses an api key for thecatapi.com stored in %s.", config.Cat.KeyPath),
		},
	}
	pluginHelp.AddCommand(pluginhelp.Command{
		Usage:       "/meow(vie) [CATegory]",
		Description: "Add a cat image to the issue or PR",
		Featured:    false,
		WhoCanUse:   "Anyone",
		Examples:    []string{"/meow", "/meow caturday", "/meowvie clothes"},
	})
	return pluginHelp, nil
}

type githubClient interface {
	CreateComment(owner, repo string, number int, comment string) error
}

type clowder interface {
	readCat(string, bool) (string, error)
}

type realClowder struct {
	url     string
	lock    sync.RWMutex
	update  time.Time
	key     string
	keyPath string
}

func (c *realClowder) setKey(keyPath string, log *logrus.Entry) {
	c.lock.Lock()
	defer c.lock.Unlock()
	if !time.Now().After(c.update) {
		return
	}
	c.update = time.Now().Add(1 * time.Minute)
	if keyPath == "" {
		c.key = ""
		return
	}
	b, err := ioutil.ReadFile(keyPath)
	if err == nil {
		c.key = strings.TrimSpace(string(b))
		return
	}
	log.WithError(err).Errorf("failed to read key at %s", keyPath)
	c.key = ""
}

type catResult struct {
	Image string `json:"url"`
}

func (cr catResult) Format() (string, error) {
	if cr.Image == "" {
		return "", errors.New("empty image url")
	}
	img, err := url.Parse(cr.Image)
	if err != nil {
		return "", fmt.Errorf("invalid image url %s: %v", cr.Image, err)
	}

	return fmt.Sprintf("![cat image](%s)", img), nil
}

func (c *realClowder) URL(category string, movieCat bool) string {
	c.lock.RLock()
	defer c.lock.RUnlock()
	uri := string(c.url)
	if category != "" {
		uri += "&category=" + url.QueryEscape(category)
	}
	if c.key != "" {
		uri += "&api_key=" + url.QueryEscape(c.key)
	}
	if movieCat {
		uri += "&mime_types=gif"
	}
	return uri
}

func (c *realClowder) readCat(category string, movieCat bool) (string, error) {
	cats := make([]catResult, 0)
	uri := c.URL(category, movieCat)
	if strings.TrimSpace(category) == "grumpy" {
		cats = append(cats, catResult{grumpyURL})
	} else {
		resp, err := http.Get(uri)
		if err != nil {
			return "", fmt.Errorf("could not read cat from %s: %v", uri, err)
		}
		defer resp.Body.Close()
		if sc := resp.StatusCode; sc > 299 || sc < 200 {
			return "", fmt.Errorf("failing %d response from %s", sc, uri)
		}
		if err = json.NewDecoder(resp.Body).Decode(&cats); err != nil {
			return "", err
		}
		if len(cats) < 1 {
			return "", fmt.Errorf("no cats in response from %s", uri)
		}
	}
	a := cats[0]
	if a.Image == "" {
		return "", fmt.Errorf("no image url in response from %s", uri)
	}
	// checking size, GitHub doesn't support big images
	toobig, err := github.ImageTooBig(a.Image)
	if err != nil {
		return "", fmt.Errorf("could not validate image size %s: %v", a.Image, err)
	} else if toobig {
		return "", fmt.Errorf("longcat is too long: %s", a.Image)
	}
	return a.Format()
}

func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
	return handle(
		pc.GitHubClient,
		pc.Logger,
		&e,
		meow,
		func() { meow.setKey(pc.PluginConfig.Cat.KeyPath, pc.Logger) },
	)
}

func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, c clowder, setKey func()) error {
	// Only consider new comments.
	if e.Action != github.GenericCommentActionCreated {
		return nil
	}
	// Make sure they are requesting a cat
	mat := match.FindStringSubmatch(e.Body)
	if mat == nil {
		return nil
	}

	category, movieCat, err := parseMatch(mat)
	if err != nil {
		return err
	}

	// Now that we know this is a relevant event we can set the key.
	setKey()

	org := e.Repo.Owner.Login
	repo := e.Repo.Name
	number := e.Number

	for i := 0; i < 3; i++ {
		resp, err := c.readCat(category, movieCat)
		if err != nil {
			log.WithError(err).Error("Failed to get cat img")
			continue
		}
		return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, resp))
	}

	var msg string
	if category != "" {
		msg = "Bad category. Please see https://api.thecatapi.com/api/categories/list"
	} else {
		msg = "https://thecatapi.com appears to be down"
	}
	if err := gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil {
		log.WithError(err).Error("Failed to leave comment")
	}

	return errors.New("could not find a valid cat image")
}

func parseMatch(mat []string) (string, bool, error) {
	if len(mat) != 3 {
		err := fmt.Errorf("expected 3 capture groups in regexp match, but got %d", len(mat))
		return "", false, err
	}
	category := strings.TrimSpace(mat[2])
	movieCat := len(mat[1]) > 0 // "vie" suffix is present.
	return category, movieCat, nil
}
<|endoftext|>


<commit_before>package containerimagebuilderpusher

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"github.com/nuclio/nuclio/pkg/errors"
	"github.com/nuclio/nuclio/pkg/processor/build/runtime"

	"github.com/nuclio/logger"
	batch_v1 "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type Kaniko struct {
	kubeClientSet        kubernetes.Interface
	logger               logger.Logger
	builderConfiguration *ContainerBuilderConfiguration
}

func NewKaniko(logger logger.Logger, kubeClientSet kubernetes.Interface,
	builderConfiguration *ContainerBuilderConfiguration) (*Kaniko, error) {

	if builderConfiguration == nil {
		return nil, errors.New("Missing kaniko builder configuration")
	}

	kanikoBuilder := &Kaniko{
		logger:               logger,
		kubeClientSet:        kubeClientSet,
		builderConfiguration: builderConfiguration,
	}

	return kanikoBuilder, nil
}

func (k *Kaniko) BuildAndPushContainerImage(buildOptions *BuildOptions, namespace string) error {
	bundleFilename, assetPath, err := k.createContainerBuildBundle(buildOptions.Image, buildOptions.ContextDir, buildOptions.TempDir)
	if err != nil {
		return errors.Wrap(err, "Failed to create container build bundle")
	}

	// Remove bundle file from NGINX assets once we are done
	defer os.Remove(assetPath) // nolint: errcheck

	// Generate kaniko job spec
	kanikoJobSpec := k.getKanikoJobSpec(namespace, buildOptions, bundleFilename)

	k.logger.DebugWith("About to publish kaniko job", "namespace", namespace, "jobSpec", kanikoJobSpec)
	kanikoJob, err := k.kubeClientSet.BatchV1().Jobs(namespace).Create(kanikoJobSpec)
	if err != nil {
		return errors.Wrap(err, "Failed to publish kaniko job")
	}

	// Cleanup
	defer k.deleteJob(namespace, kanikoJob.Name) // nolint: errcheck

	// Wait for kaniko to finish
	return k.waitForKanikoJobCompletion(namespace, kanikoJob.Name, buildOptions.BuildTimeoutSeconds)
}

func (k *Kaniko) GetOnbuildStages(onbuildArtifacts []runtime.Artifact) ([]string, error) {
	onbuildStages := make([]string, len(onbuildArtifacts))
	stage := 0

	for _, artifact := range onbuildArtifacts {
		if artifact.ExternalImage {
			continue
		}

		stage++
		if len(artifact.Name) == 0 {
			artifact.Name = fmt.Sprintf("onbuildStage-%d", stage)
		}

		baseImage := fmt.Sprintf("FROM %s AS %s", artifact.Image, artifact.Name)
		onbuildDockerfileContents := fmt.Sprintf(`%s
ARG NUCLIO_LABEL
ARG NUCLIO_ARCH
`, baseImage)

		onbuildStages = append(onbuildStages, onbuildDockerfileContents)
	}

	return onbuildStages, nil
}

func (k *Kaniko) TransformOnbuildArtifactPaths(onbuildArtifacts []runtime.Artifact) (map[string]string, error) {

	stagedArtifactPaths := make(map[string]string)
	for _, artifact := range onbuildArtifacts {
		for source, destination := range artifact.Paths {
			var transformedSource string
			if artifact.ExternalImage {

				// Using external image as "stage"
				// Example: COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
				transformedSource = fmt.Sprintf("--from=%s %s", artifact.Image, source)
			} else {

				// Using previously build image with index `artifactIndex` as "stage"
				transformedSource = fmt.Sprintf("--from=%s %s", artifact.Name, source)
			}
			stagedArtifactPaths[transformedSource] = destination
		}
	}
	return stagedArtifactPaths, nil
}

func (k *Kaniko) GetBaseImageRegistry(registry string) string {
	return registry
}

func (k *Kaniko) createContainerBuildBundle(image string, contextDir string, tempDir string) (string, string, error) {
	var err error

	// Create temp directory to store compressed container build bundle
	buildContainerBundleDir := path.Join(tempDir, "tar")
	err = os.Mkdir(buildContainerBundleDir, 0744)
	if err != nil {
		return "", "", errors.Wrapf(err, "Failed to create tar dir: %s", buildContainerBundleDir)
	}

	k.logger.DebugWith("Created tar dir", "dir", buildContainerBundleDir)

	tarFilename := fmt.Sprintf("%s.tar.gz", strings.Replace(image, "/", "_", -1))
	tarFilename = strings.Replace(tarFilename, ":", "_", -1)
	tarFilePath := path.Join(buildContainerBundleDir, tarFilename)

	k.logger.DebugWith("Compressing build bundle", "tarFilePath", tarFilePath)

	// Just in case file with the same filename already exist - delete it
	_ = os.Remove(tarFilePath)

	_, err = exec.Command("tar", "-zcvf", tarFilePath, contextDir).Output()
	if err != nil {
		return "", "", errors.Wrapf(err, "Failed to compress build bundle")
	}
	k.logger.Debug("Build bundle was successfully compressed")

	// Create symlink to bundle tar file in nginx serving directory
	assetPath := path.Join("/etc/nginx/static/assets", tarFilename)
	err = os.Link(tarFilePath, assetPath)
	if err != nil {
		return "", "", errors.Wrapf(err, "Failed to create symlink to build bundle")
	}

	return tarFilename, assetPath, nil
}

func (k *Kaniko) getKanikoJobSpec(namespace string, buildOptions *BuildOptions, bundleFilename string) *batch_v1.Job {

	completions := int32(1)
	backoffLimit := int32(0)
	buildArgs := []string{
		fmt.Sprintf("--dockerfile=%s", buildOptions.DockerfileInfo.DockerfilePath),
		fmt.Sprintf("--context=%s", buildOptions.ContextDir),
		fmt.Sprintf("--destination=%s/%s", buildOptions.RegistryURL, buildOptions.Image),
	}

	if !buildOptions.NoCache {
		buildArgs = append(buildArgs, "--cache=true")
	}

	if k.builderConfiguration.InsecureRegistry {
		buildArgs = append(buildArgs, "--insecure")
		buildArgs = append(buildArgs, "--insecure-pull")
	}

	// Add build options args
	for k, v := range buildOptions.BuildArgs {
		buildArgs = append(buildArgs, fmt.Sprintf("--build-arg=%s=%s", k, v))
	}

	volumeMount := v1.VolumeMount{
		Name:      "tmp",
		MountPath: "/tmp",
	}

	functionName := strings.Replace(buildOptions.Image, "/", "-", -1)
	functionName = strings.Replace(functionName, ":", "-", -1)
	jobName := fmt.Sprintf("%s.%s.%d", k.builderConfiguration.JobPrefix, functionName, time.Now().Unix())

	kanikoJobSpec := &batch_v1.Job{
		ObjectMeta: meta_v1.ObjectMeta{
			Name:      jobName,
			Namespace: namespace,
		},
		Spec: batch_v1.JobSpec{
			Completions:           &completions,
			ActiveDeadlineSeconds: &buildOptions.BuildTimeoutSeconds,
			BackoffLimit:          &backoffLimit,
			Template: v1.PodTemplateSpec{
				ObjectMeta: meta_v1.ObjectMeta{
					Name:      jobName,
					Namespace: namespace,
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:         "kaniko-executor",
							Image:        k.builderConfiguration.KanikoImage,
							Args:         buildArgs,
							VolumeMounts: []v1.VolumeMount{volumeMount},
						},
					},
					InitContainers: []v1.Container{
						{
							Name:  "fetch-bundle",
							Image: k.builderConfiguration.BusyBoxImage,
							Command: []string{
								"wget",
								fmt.Sprintf("http://%s:8070/assets/%s", os.Getenv("NUCLIO_DASHBOARD_DEPLOYMENT_NAME"), bundleFilename),
								"-P",
								"/tmp",
							},
							VolumeMounts: []v1.VolumeMount{volumeMount},
						},
						{
							Name:  "extract-bundle",
							Image: k.builderConfiguration.BusyBoxImage,
							Command: []string{
								"tar",
								"-xvf",
								fmt.Sprintf("/tmp/%s", bundleFilename),
								"-C",
								"/",
							},
							VolumeMounts: []v1.VolumeMount{volumeMount},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: "tmp",
							VolumeSource: v1.VolumeSource{
								EmptyDir: &v1.EmptyDirVolumeSource{},
							},
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
				},
			},
		},
	}

	return kanikoJobSpec
}

func (k *Kaniko) waitForKanikoJobCompletion(namespace string, jobName string, BuildTimeoutSeconds int64) error {
	k.logger.DebugWith("Waiting for kaniko to finish", "BuildTimeoutSeconds", BuildTimeoutSeconds)
	timeout := time.Now().Add(time.Duration(BuildTimeoutSeconds) * time.Second)
	for time.Now().Before(timeout) {
		runningJob, err := k.kubeClientSet.BatchV1().Jobs(namespace).
			Get(jobName, meta_v1.GetOptions{IncludeUninitialized: true})

		if err != nil {
			return errors.Wrap(err, "Failed to poll kaniko job status")
		}

		if runningJob.Status.Succeeded > 0 {
			k.logger.Debug("Kaniko job was completed successfully")
			return nil
		}
		if runningJob.Status.Failed > 0 {
			jobLogs, err := k.getJobLogs(namespace, jobName)
			if err != nil {
				return errors.Wrap(err, "Failed to retrieve kaniko job logs")
			}
			return fmt.Errorf("kaniko job has failed: %s", jobLogs)
		}

		time.Sleep(10 * time.Second)
	}
	jobLogs, err := k.getJobLogs(namespace, jobName)
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve kaniko job logs")
	}
	return fmt.Errorf("kaniko job has timed out: %s", jobLogs)
}

func (k *Kaniko) getJobLogs(namespace string, jobName string) (string, error) {
	k.logger.DebugWith("Fetching kaniko job logs", "namespace", namespace, "job", jobName)

	// list pods
	jobPods, err := k.kubeClientSet.CoreV1().Pods(namespace).List(meta_v1.ListOptions{
		LabelSelector: fmt.Sprintf("job-name=%s", jobName),
	})

	if err != nil {
		return "", errors.Wrapf(err, "Failed to list job's pods")
	}
	if len(jobPods.Items) == 0 {
		return "", errors.New("No pods found for job")
	}
	if len(jobPods.Items) > 1 {
		return "", errors.New("Got too many job pods")
	}

	// find job pod
	restClientRequest := k.kubeClientSet.
		CoreV1().
		Pods(namespace).
		GetLogs(jobPods.Items[0].Name, &v1.PodLogOptions{})

	restReadCloser, err := restClientRequest.Stream()
	if err != nil {
		return "", errors.Wrap(err, "Failed to get log read/closer")
	}

	defer restReadCloser.Close() // nolint: errcheck

	logContents, err := ioutil.ReadAll(restReadCloser)
	if err != nil {
		return "", errors.Wrap(err, "Failed to read logs")
	}

	return string(logContents), nil
}

func (k *Kaniko) deleteJob(namespace string, jobName string) error {
	k.logger.DebugWith("Deleting kaniko job", "namespace", namespace, "job", jobName)

	propagationPolicy := meta_v1.DeletePropagationBackground
	if err := k.kubeClientSet.BatchV1().Jobs(namespace).Delete(jobName, &meta_v1.DeleteOptions{
		PropagationPolicy: &propagationPolicy,
	}); err != nil {
		return errors.Wrap(err, "Failed to delete kaniko job")
	}
	return nil
}
<commit_msg>Truncate kaniko job name to match k8s limit of 63 (#1449)<commit_after>package containerimagebuilderpusher

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"github.com/nuclio/nuclio/pkg/errors"
	"github.com/nuclio/nuclio/pkg/processor/build/runtime"

	"github.com/nuclio/logger"
	batch_v1 "k8s.io/api/batch/v1"
	v1 "k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type Kaniko struct {
	kubeClientSet        kubernetes.Interface
	logger               logger.Logger
	builderConfiguration *ContainerBuilderConfiguration
}

func NewKaniko(logger logger.Logger, kubeClientSet kubernetes.Interface,
	builderConfiguration *ContainerBuilderConfiguration) (*Kaniko, error) {

	if builderConfiguration == nil {
		return nil, errors.New("Missing kaniko builder configuration")
	}

	kanikoBuilder := &Kaniko{
		logger:               logger,
		kubeClientSet:        kubeClientSet,
		builderConfiguration: builderConfiguration,
	}

	return kanikoBuilder, nil
}

func (k *Kaniko) BuildAndPushContainerImage(buildOptions *BuildOptions, namespace string) error {
	bundleFilename, assetPath, err := k.createContainerBuildBundle(buildOptions.Image, buildOptions.ContextDir, buildOptions.TempDir)
	if err != nil {
		return errors.Wrap(err, "Failed to create container build bundle")
	}

	// Remove bundle file from NGINX assets once we are done
	defer os.Remove(assetPath) // nolint: errcheck

	// Generate kaniko job spec
	kanikoJobSpec := k.getKanikoJobSpec(namespace, buildOptions, bundleFilename)

	k.logger.DebugWith("About to publish kaniko job", "namespace", namespace, "jobSpec", kanikoJobSpec)
	kanikoJob, err := k.kubeClientSet.BatchV1().Jobs(namespace).Create(kanikoJobSpec)
	if err != nil {
		return errors.Wrap(err, "Failed to publish kaniko job")
	}

	// Cleanup
	defer k.deleteJob(namespace, kanikoJob.Name) // nolint: errcheck

	// Wait for kaniko to finish
	return k.waitForKanikoJobCompletion(namespace, kanikoJob.Name, buildOptions.BuildTimeoutSeconds)
}

func (k *Kaniko) GetOnbuildStages(onbuildArtifacts []runtime.Artifact) ([]string, error) {
	onbuildStages := make([]string, len(onbuildArtifacts))
	stage := 0

	for _, artifact := range onbuildArtifacts {
		if artifact.ExternalImage {
			continue
		}

		stage++
		if len(artifact.Name) == 0 {
			artifact.Name = fmt.Sprintf("onbuildStage-%d", stage)
		}

		baseImage := fmt.Sprintf("FROM %s AS %s", artifact.Image, artifact.Name)
		onbuildDockerfileContents := fmt.Sprintf(`%s
ARG NUCLIO_LABEL
ARG NUCLIO_ARCH
`, baseImage)

		onbuildStages = append(onbuildStages, onbuildDockerfileContents)
	}

	return onbuildStages, nil
}

func (k *Kaniko) TransformOnbuildArtifactPaths(onbuildArtifacts []runtime.Artifact) (map[string]string, error) {

	stagedArtifactPaths := make(map[string]string)
	for _, artifact := range onbuildArtifacts {
		for source, destination := range artifact.Paths {
			var transformedSource string
			if artifact.ExternalImage {

				// Using external image as "stage"
				// Example: COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
				transformedSource = fmt.Sprintf("--from=%s %s", artifact.Image, source)
			} else {

				// Using previously build image with index `artifactIndex` as "stage"
				transformedSource = fmt.Sprintf("--from=%s %s", artifact.Name, source)
			}
			stagedArtifactPaths[transformedSource] = destination
		}
	}
	return stagedArtifactPaths, nil
}

func (k *Kaniko) GetBaseImageRegistry(registry string) string {
	return registry
}

func (k *Kaniko) createContainerBuildBundle(image string, contextDir string, tempDir string) (string, string, error) {
	var err error

	// Create temp directory to store compressed container build bundle
	buildContainerBundleDir := path.Join(tempDir, "tar")
	err = os.Mkdir(buildContainerBundleDir, 0744)
	if err != nil {
		return "", "", errors.Wrapf(err, "Failed to create tar dir: %s", buildContainerBundleDir)
	}

	k.logger.DebugWith("Created tar dir", "dir", buildContainerBundleDir)

	tarFilename := fmt.Sprintf("%s.tar.gz", strings.Replace(image, "/", "_", -1))
	tarFilename = strings.Replace(tarFilename, ":", "_", -1)
	tarFilePath := path.Join(buildContainerBundleDir, tarFilename)

	k.logger.DebugWith("Compressing build bundle", "tarFilePath", tarFilePath)

	// Just in case file with the same filename already exist - delete it
	_ = os.Remove(tarFilePath)

	_, err = exec.Command("tar", "-zcvf", tarFilePath, contextDir).Output()
	if err != nil {
		return "", "", errors.Wrapf(err, "Failed to compress build bundle")
	}
	k.logger.Debug("Build bundle was successfully compressed")

	// Create symlink to bundle tar file in nginx serving directory
	assetPath := path.Join("/etc/nginx/static/assets", tarFilename)
	err = os.Link(tarFilePath, assetPath)
	if err != nil {
		return "", "", errors.Wrapf(err, "Failed to create symlink to build bundle")
	}

	return tarFilename, assetPath, nil
}

func (k *Kaniko) getKanikoJobSpec(namespace string, buildOptions *BuildOptions, bundleFilename string) *batch_v1.Job {

	completions := int32(1)
	backoffLimit := int32(0)
	buildArgs := []string{
		fmt.Sprintf("--dockerfile=%s", buildOptions.DockerfileInfo.DockerfilePath),
		fmt.Sprintf("--context=%s", buildOptions.ContextDir),
		fmt.Sprintf("--destination=%s/%s", buildOptions.RegistryURL, buildOptions.Image),
	}

	if !buildOptions.NoCache {
		buildArgs = append(buildArgs, "--cache=true")
	}

	if k.builderConfiguration.InsecureRegistry {
		buildArgs = append(buildArgs, "--insecure")
		buildArgs = append(buildArgs, "--insecure-pull")
	}

	// Add build options args
	for k, v := range buildOptions.BuildArgs {
		buildArgs = append(buildArgs, fmt.Sprintf("--build-arg=%s=%s", k, v))
	}

	volumeMount := v1.VolumeMount{
		Name:      "tmp",
		MountPath: "/tmp",
	}

	functionName := strings.Replace(buildOptions.Image, "/", "-", -1)
	functionName = strings.Replace(functionName, ":", "-", -1)
	timestamp := fmt.Sprintf("%d", time.Now().Unix())

	// Truncate function name so the job name won't exceed k8s limit of 63
	functionNameLimit := 63 - (len(k.builderConfiguration.JobPrefix) + len(timestamp) + 2)
	functionName = functionName[0:functionNameLimit]
	jobName := fmt.Sprintf("%s.%s.%s", k.builderConfiguration.JobPrefix, functionName, timestamp)

	kanikoJobSpec := &batch_v1.Job{
		ObjectMeta: meta_v1.ObjectMeta{
			Name:      jobName,
			Namespace: namespace,
		},
		Spec: batch_v1.JobSpec{
			Completions:           &completions,
			ActiveDeadlineSeconds: &buildOptions.BuildTimeoutSeconds,
			BackoffLimit:          &backoffLimit,
			Template: v1.PodTemplateSpec{
				ObjectMeta: meta_v1.ObjectMeta{
					Name:      jobName,
					Namespace: namespace,
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:         "kaniko-executor",
							Image:        k.builderConfiguration.KanikoImage,
							Args:         buildArgs,
							VolumeMounts: []v1.VolumeMount{volumeMount},
						},
					},
					InitContainers: []v1.Container{
						{
							Name:  "fetch-bundle",
							Image: k.builderConfiguration.BusyBoxImage,
							Command: []string{
								"wget",
								fmt.Sprintf("http://%s:8070/assets/%s", os.Getenv("NUCLIO_DASHBOARD_DEPLOYMENT_NAME"), bundleFilename),
								"-P",
								"/tmp",
							},
							VolumeMounts: []v1.VolumeMount{volumeMount},
						},
						{
							Name:  "extract-bundle",
							Image: k.builderConfiguration.BusyBoxImage,
							Command: []string{
								"tar",
								"-xvf",
								fmt.Sprintf("/tmp/%s", bundleFilename),
								"-C",
								"/",
							},
							VolumeMounts: []v1.VolumeMount{volumeMount},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: "tmp",
							VolumeSource: v1.VolumeSource{
								EmptyDir: &v1.EmptyDirVolumeSource{},
							},
						},
					},
					RestartPolicy: v1.RestartPolicyNever,
				},
			},
		},
	}

	return kanikoJobSpec
}

func (k *Kaniko) waitForKanikoJobCompletion(namespace string, jobName string, BuildTimeoutSeconds int64) error {
	k.logger.DebugWith("Waiting for kaniko to finish", "BuildTimeoutSeconds", BuildTimeoutSeconds)
	timeout := time.Now().Add(time.Duration(BuildTimeoutSeconds) * time.Second)
	for time.Now().Before(timeout) {
		runningJob, err := k.kubeClientSet.BatchV1().Jobs(namespace).
			Get(jobName, meta_v1.GetOptions{IncludeUninitialized: true})

		if err != nil {
			return errors.Wrap(err, "Failed to poll kaniko job status")
		}

		if runningJob.Status.Succeeded > 0 {
			k.logger.Debug("Kaniko job was completed successfully")
			return nil
		}
		if runningJob.Status.Failed > 0 {
			jobLogs, err := k.getJobLogs(namespace, jobName)
			if err != nil {
				return errors.Wrap(err, "Failed to retrieve kaniko job logs")
			}
			return fmt.Errorf("kaniko job has failed: %s", jobLogs)
		}

		time.Sleep(10 * time.Second)
	}
	jobLogs, err := k.getJobLogs(namespace, jobName)
	if err != nil {
		return errors.Wrap(err, "Failed to retrieve kaniko job logs")
	}
	return fmt.Errorf("kaniko job has timed out: %s", jobLogs)
}

func (k *Kaniko) getJobLogs(namespace string, jobName string) (string, error) {
	k.logger.DebugWith("Fetching kaniko job logs", "namespace", namespace, "job", jobName)

	// list pods
	jobPods, err := k.kubeClientSet.CoreV1().Pods(namespace).List(meta_v1.ListOptions{
		LabelSelector: fmt.Sprintf("job-name=%s", jobName),
	})

	if err != nil {
		return "", errors.Wrapf(err, "Failed to list job's pods")
	}
	if len(jobPods.Items) == 0 {
		return "", errors.New("No pods found for job")
	}
	if len(jobPods.Items) > 1 {
		return "", errors.New("Got too many job pods")
	}

	// find job pod
	restClientRequest := k.kubeClientSet.
		CoreV1().
		Pods(namespace).
		GetLogs(jobPods.Items[0].Name, &v1.PodLogOptions{})

	restReadCloser, err := restClientRequest.Stream()
	if err != nil {
		return "", errors.Wrap(err, "Failed to get log read/closer")
	}

	defer restReadCloser.Close() // nolint: errcheck

	logContents, err := ioutil.ReadAll(restReadCloser)
	if err != nil {
		return "", errors.Wrap(err, "Failed to read logs")
	}

	return string(logContents), nil
}

func (k *Kaniko) deleteJob(namespace string, jobName string) error {
	k.logger.DebugWith("Deleting kaniko job", "namespace", namespace, "job", jobName)

	propagationPolicy := meta_v1.DeletePropagationBackground
	if err := k.kubeClientSet.BatchV1().Jobs(namespace).Delete(jobName, &meta_v1.DeleteOptions{
		PropagationPolicy: &propagationPolicy,
	}); err != nil {
		return errors.Wrap(err, "Failed to delete kaniko job")
	}
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The cert-manager Authors.

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

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

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

package controller

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes"
	networkinglisters "k8s.io/client-go/listers/networking/v1beta1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"

	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/issuer"
	logf "github.com/jetstack/cert-manager/pkg/logs"
)

const (
	ControllerName = "ingress-shim"
)

type defaults struct {
	autoCertificateAnnotations          []string
	issuerName, issuerKind, issuerGroup string
}

type controller struct {
	kClient  kubernetes.Interface
	cmClient clientset.Interface

	recorder record.EventRecorder
	log      logr.Logger

	ingressLister       networkinglisters.IngressLister
	certificateLister   cmlisters.CertificateLister
	issuerLister        cmlisters.IssuerLister
	clusterIssuerLister cmlisters.ClusterIssuerLister

	helper   issuer.Helper
	defaults defaults
}

// Register registers and constructs the controller using the provided context.
// It returns the workqueue to be used to enqueue items, a list of
// InformerSynced functions that must be synced, or an error.
func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	kShared := ctx.KubeSharedInformerFactory
	cmShared := ctx.SharedInformerFactory

	c.log = logf.FromContext(ctx.RootContext, ControllerName)
	queue := workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName)

	mustSync := []cache.InformerSynced{
		cmShared.Certmanager().V1().Certificates().Informer().HasSynced,
		cmShared.Certmanager().V1().Issuers().Informer().HasSynced,
	}

	c.ingressLister = kShared.Networking().V1beta1().Ingresses().Lister()
	c.certificateLister = cmShared.Certmanager().V1().Certificates().Lister()
	c.issuerLister = cmShared.Certmanager().V1().Issuers().Lister()

	// We don't need to run the ClusterIssuer controller when cert-manager is
	// running in non-namespaced mode (i.e. --namespace="").
	if ctx.Namespace == "" {
		mustSync = append(mustSync, cmShared.Certmanager().V1().ClusterIssuers().Informer().HasSynced)
		c.clusterIssuerLister = cmShared.Certmanager().V1().ClusterIssuers().Lister()
	}

	kShared.Networking().V1beta1().Ingresses().Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{
		Queue: queue,
	})
	cmShared.Certmanager().V1().Certificates().Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		WorkFunc: certificateDeleted(queue),
	})

	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
	c.kClient = ctx.Client
	c.cmClient = ctx.CMClient
	c.recorder = ctx.Recorder
	c.defaults = defaults{
		ctx.DefaultAutoCertificateAnnotations,
		ctx.DefaultIssuerName,
		ctx.DefaultIssuerKind,
		ctx.DefaultIssuerGroup,
	}

	return queue, mustSync, nil
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))
		return nil
	}

	crt, err := c.ingressLister.Ingresses(namespace).Get(name)

	if err != nil {
		if k8sErrors.IsNotFound(err) {
			runtime.HandleError(fmt.Errorf("ingress '%s' in work queue no longer exists", key))
			return nil
		}

		return err
	}

	return c.sync(ctx, crt)
}

// Whenever a Certificate gets deleted, we want to reconcile its parent Ingress.
// This parent Ingress is called "controller object". For example, the following
// Certificate is controlled by the Ingress "example":
//
//     kind: Certificate
//     metadata:
//       namespace: cert-that-was-deleted
//       ownerReferences:
//       - controller: true                                       ← this
//         apiVersion: networking.k8s.io/v1beta1
//         kind: Ingress
//         name: example
//         blockOwnerDeletion: true
//         uid: 7d3897c2-ce27-4144-883a-e1b5f89bd65a
func certificateDeleted(queue workqueue.RateLimitingInterface) func(obj interface{}) {
	return func(obj interface{}) {
		cert, ok := obj.(*cmapi.Certificate)
		if !ok {
			runtime.HandleError(fmt.Errorf("not a Certificate object: %#v", obj))
			return
		}

		ingress := metav1.GetControllerOf(cert)
		if ingress == nil {
			// No controller should care about orphans being deleted or
			// updated.
			return
		}

		// We don't check the apiVersion e.g. "networking.k8s.io/v1beta1"
		// because there is no chance that another object called "Ingress" be
		// the controller of a Certificate.
		if ingress.Kind != "Ingress" {
			return
		}

		// Owner references don't know about the namespace of the referenced
		// object. That's because owner refs do not support cross-namespace
		// references. We also know that the Certificate and its parent Ingress
		// must both be on the same namespace. We thus use the Certificate's
		// namespace to trigger a resync of the parent Ingress (the string below
		// is a "key" of the form "namespace-1/my-ingress").
		queue.Add(cert.Namespace + "/" + ingress.Name)
	}
}

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(&controller{}).
			Complete()
	})
}
<commit_msg>ingress-shim: remove unecessary/verbose comment<commit_after>/*
Copyright 2020 The cert-manager Authors.

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

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

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

package controller

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes"
	networkinglisters "k8s.io/client-go/listers/networking/v1beta1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"

	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	clientset "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/issuer"
	logf "github.com/jetstack/cert-manager/pkg/logs"
)

const (
	ControllerName = "ingress-shim"
)

type defaults struct {
	autoCertificateAnnotations          []string
	issuerName, issuerKind, issuerGroup string
}

type controller struct {
	kClient  kubernetes.Interface
	cmClient clientset.Interface

	recorder record.EventRecorder
	log      logr.Logger

	ingressLister       networkinglisters.IngressLister
	certificateLister   cmlisters.CertificateLister
	issuerLister        cmlisters.IssuerLister
	clusterIssuerLister cmlisters.ClusterIssuerLister

	helper   issuer.Helper
	defaults defaults
}

// Register registers and constructs the controller using the provided context.
// It returns the workqueue to be used to enqueue items, a list of
// InformerSynced functions that must be synced, or an error.
func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	kShared := ctx.KubeSharedInformerFactory
	cmShared := ctx.SharedInformerFactory

	c.log = logf.FromContext(ctx.RootContext, ControllerName)
	queue := workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName)

	mustSync := []cache.InformerSynced{
		cmShared.Certmanager().V1().Certificates().Informer().HasSynced,
		cmShared.Certmanager().V1().Issuers().Informer().HasSynced,
	}

	c.ingressLister = kShared.Networking().V1beta1().Ingresses().Lister()
	c.certificateLister = cmShared.Certmanager().V1().Certificates().Lister()
	c.issuerLister = cmShared.Certmanager().V1().Issuers().Lister()

	// We don't need to run the ClusterIssuer controller when cert-manager is
	// running in non-namespaced mode (i.e. --namespace="").
	if ctx.Namespace == "" {
		mustSync = append(mustSync, cmShared.Certmanager().V1().ClusterIssuers().Informer().HasSynced)
		c.clusterIssuerLister = cmShared.Certmanager().V1().ClusterIssuers().Lister()
	}

	kShared.Networking().V1beta1().Ingresses().Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{
		Queue: queue,
	})
	cmShared.Certmanager().V1().Certificates().Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		WorkFunc: certificateDeleted(queue),
	})

	c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister)
	c.kClient = ctx.Client
	c.cmClient = ctx.CMClient
	c.recorder = ctx.Recorder
	c.defaults = defaults{
		ctx.DefaultAutoCertificateAnnotations,
		ctx.DefaultIssuerName,
		ctx.DefaultIssuerKind,
		ctx.DefaultIssuerGroup,
	}

	return queue, mustSync, nil
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		runtime.HandleError(fmt.Errorf("invalid resource key: %s", key))
		return nil
	}

	crt, err := c.ingressLister.Ingresses(namespace).Get(name)

	if err != nil {
		if k8sErrors.IsNotFound(err) {
			runtime.HandleError(fmt.Errorf("ingress '%s' in work queue no longer exists", key))
			return nil
		}

		return err
	}

	return c.sync(ctx, crt)
}

// Whenever a Certificate gets deleted, we want to reconcile its parent Ingress.
// This parent Ingress is called "controller object". For example, the following
// Certificate is controlled by the Ingress "example":
//
//     kind: Certificate
//     metadata:
//       namespace: cert-that-was-deleted
//       ownerReferences:
//       - controller: true                                       ← this
//         apiVersion: networking.k8s.io/v1beta1
//         kind: Ingress
//         name: example
//         blockOwnerDeletion: true
//         uid: 7d3897c2-ce27-4144-883a-e1b5f89bd65a
//
// Note that the owner reference doesn't know about the Ingress's namespace.
func certificateDeleted(queue workqueue.RateLimitingInterface) func(obj interface{}) {
	return func(obj interface{}) {
		cert, ok := obj.(*cmapi.Certificate)
		if !ok {
			runtime.HandleError(fmt.Errorf("not a Certificate object: %#v", obj))
			return
		}

		ingress := metav1.GetControllerOf(cert)
		if ingress == nil {
			// No controller should care about orphans being deleted or
			// updated.
			return
		}

		// We don't check the apiVersion e.g. "networking.k8s.io/v1beta1"
		// because there is no chance that another object called "Ingress" be
		// the controller of a Certificate.
		if ingress.Kind != "Ingress" {
			return
		}

		queue.Add(cert.Namespace + "/" + ingress.Name)
	}
}

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(&controller{}).
			Complete()
	})
}
<|endoftext|>


<commit_before>package diff

import (
	"github.com/Aptomi/aptomi/pkg/engine/apply/action"
	"github.com/Aptomi/aptomi/pkg/engine/apply/action/component"
	"github.com/Aptomi/aptomi/pkg/engine/apply/action/global"
	"github.com/Aptomi/aptomi/pkg/engine/resolve"
	"github.com/Aptomi/aptomi/pkg/runtime"
)

// PolicyResolutionDiff represents a difference between two policy resolution data structs (actual and desired states)
type PolicyResolutionDiff struct {
	// Prev is actual policy resolution data
	Prev *resolve.PolicyResolution

	// Next is desired policy resolution data
	Next *resolve.PolicyResolution

	// Actions is a generated, ordered list of actions that need to be executed in order to get from an actual state to the desired state
	Actions []action.Base

	Revision runtime.Generation
}

// NewPolicyResolutionDiff calculates difference between two given policy resolution structs (actual and desired states).
// It iterates over all component instances and figures out which component instances have to be instantiated (new
// consumers appeared and they didn't exist before), which component instances have to be updated (e.g. parameters changed), which component
// instances have to be destroyed (that have no consumers left), and so on.
func NewPolicyResolutionDiff(next *resolve.PolicyResolution, prev *resolve.PolicyResolution, revision runtime.Generation) *PolicyResolutionDiff {
	result := &PolicyResolutionDiff{
		Prev:     prev,
		Next:     next,
		Actions:  []action.Base{},
		Revision: revision,
	}
	result.compareAndProduceActions()
	return result
}

func appendUpdateAction(actions []action.Base, updateActions map[string]bool, updateAction *component.UpdateAction) []action.Base {
	if !updateActions[updateAction.GetName()] {
		updateActions[updateAction.GetName()] = true
		actions = append(actions, updateAction)
	}

	return actions
}

// On a component level -- see which component instance keys appear and disappear
// TODO: reduce cyclomatic complexity
func (diff *PolicyResolutionDiff) compareAndProduceActions() { // nolint: gocyclo
	actions := make(map[string][]action.Base)

	updateActions := make(map[string]bool)
	endpointsActions := make([]action.Base, 0)

	// merge all instance keys from prev and next
	allKeys := make(map[string]bool)
	for key := range diff.Prev.ComponentInstanceMap {
		allKeys[key] = true
	}
	for key := range diff.Next.ComponentInstanceMap {
		allKeys[key] = true
	}

	// go over all the keys and see which one appear and which one disappear
	for instanceKey := range allKeys {
		prevInstance := diff.Prev.ComponentInstanceMap[instanceKey]
		nextInstance := diff.Next.ComponentInstanceMap[instanceKey]

		var depKeysPrev map[string]bool
		if prevInstance != nil {
			depKeysPrev = prevInstance.DependencyKeys
		}

		var depKeysNext map[string]bool
		if nextInstance != nil {
			depKeysNext = nextInstance.DependencyKeys
		}

		createOrUpdate := false

		// see if a component needs to be instantiated
		if len(depKeysPrev) <= 0 && len(depKeysNext) > 0 {
			createOrUpdate = true
			actions[instanceKey] = append(actions[instanceKey], component.NewCreateAction(diff.Revision, instanceKey))
		}

		// see if a component needs to be destructed
		if len(depKeysPrev) > 0 && len(depKeysNext) <= 0 {
			actions[instanceKey] = append(actions[instanceKey], component.NewDeleteAction(diff.Revision, instanceKey))
		}

		// see if a component needs to be updated
		if len(depKeysPrev) > 0 && len(depKeysNext) > 0 {
			sameParams := prevInstance.CalculatedCodeParams.DeepEqual(nextInstance.CalculatedCodeParams)
			if !sameParams {
				createOrUpdate = true

				actions[instanceKey] = appendUpdateAction(actions[instanceKey], updateActions, component.NewUpdateAction(diff.Revision, instanceKey))

				// if it has a parent service, indicate that it basically gets updated as well
				// this is required for adjusting update/creation times of a service with changed component
				// this may produce duplicate "update" actions for the parent service
				if nextInstance.Metadata.Key.IsComponent() {
					serviceKey := nextInstance.Metadata.Key.GetParentServiceKey().GetKey()
					actions[serviceKey] = appendUpdateAction(actions[serviceKey], updateActions, component.NewUpdateAction(diff.Revision, serviceKey))
				}
			}
		}

		// see if a user needs to be detached from a component
		for dependencyID := range depKeysPrev {
			if !depKeysNext[dependencyID] {
				actions[instanceKey] = append(actions[instanceKey], component.NewDetachDependencyAction(diff.Revision, instanceKey, dependencyID))
			}
		}

		// see if a user needs to be attached to a component
		for dependencyID := range depKeysNext {
			if !depKeysPrev[dependencyID] {
				actions[instanceKey] = append(actions[instanceKey], component.NewAttachDependencyAction(diff.Revision, instanceKey, dependencyID))
			}
		}

		if createOrUpdate {
			endpointsActions = append(endpointsActions, component.NewEndpointsAction(diff.Revision, instanceKey))
		}
	}

	// Generate actions in the right order
	for _, key := range diff.Next.ComponentProcessingOrder {
		actionList, found := actions[key]
		if found {
			diff.Actions = append(diff.Actions, actionList...)
			delete(actions, key)
		}
	}
	for _, key := range diff.Prev.ComponentProcessingOrder {
		actionList, found := actions[key]
		if found {
			diff.Actions = append(diff.Actions, actionList...)
			delete(actions, key)
		}
	}

	diff.Actions = append(diff.Actions, endpointsActions...)

	// call a global post-processing action
	if len(diff.Actions) > 0 {
		diff.Actions = append(diff.Actions, global.NewPostProcessAction(diff.Revision))
	}
}
<commit_msg>Fix missing endpoints after attach/detach events<commit_after>package diff

import (
	"github.com/Aptomi/aptomi/pkg/engine/apply/action"
	"github.com/Aptomi/aptomi/pkg/engine/apply/action/component"
	"github.com/Aptomi/aptomi/pkg/engine/apply/action/global"
	"github.com/Aptomi/aptomi/pkg/engine/resolve"
	"github.com/Aptomi/aptomi/pkg/runtime"
)

// PolicyResolutionDiff represents a difference between two policy resolution data structs (actual and desired states)
type PolicyResolutionDiff struct {
	// Prev is actual policy resolution data
	Prev *resolve.PolicyResolution

	// Next is desired policy resolution data
	Next *resolve.PolicyResolution

	// Actions is a generated, ordered list of actions that need to be executed in order to get from an actual state to the desired state
	Actions []action.Base

	Revision runtime.Generation
}

// NewPolicyResolutionDiff calculates difference between two given policy resolution structs (actual and desired states).
// It iterates over all component instances and figures out which component instances have to be instantiated (new
// consumers appeared and they didn't exist before), which component instances have to be updated (e.g. parameters changed), which component
// instances have to be destroyed (that have no consumers left), and so on.
func NewPolicyResolutionDiff(next *resolve.PolicyResolution, prev *resolve.PolicyResolution, revision runtime.Generation) *PolicyResolutionDiff {
	result := &PolicyResolutionDiff{
		Prev:     prev,
		Next:     next,
		Actions:  []action.Base{},
		Revision: revision,
	}
	result.compareAndProduceActions()
	return result
}

func appendUpdateAction(actions []action.Base, updateActions map[string]bool, updateAction *component.UpdateAction) []action.Base {
	if !updateActions[updateAction.GetName()] {
		updateActions[updateAction.GetName()] = true
		actions = append(actions, updateAction)
	}

	return actions
}

// On a component level -- see which component instance keys appear and disappear
// TODO: reduce cyclomatic complexity
func (diff *PolicyResolutionDiff) compareAndProduceActions() { // nolint: gocyclo
	actions := make(map[string][]action.Base)

	updateActions := make(map[string]bool)
	endpointsActions := make([]action.Base, 0)

	// merge all instance keys from prev and next
	allKeys := make(map[string]bool)
	for key := range diff.Prev.ComponentInstanceMap {
		allKeys[key] = true
	}
	for key := range diff.Next.ComponentInstanceMap {
		allKeys[key] = true
	}

	// go over all the keys and see which one appear and which one disappear
	for instanceKey := range allKeys {
		prevInstance := diff.Prev.ComponentInstanceMap[instanceKey]
		nextInstance := diff.Next.ComponentInstanceMap[instanceKey]

		var depKeysPrev map[string]bool
		if prevInstance != nil {
			depKeysPrev = prevInstance.DependencyKeys
		}

		var depKeysNext map[string]bool
		if nextInstance != nil {
			depKeysNext = nextInstance.DependencyKeys
		}

		componentChanged := false

		// see if a component needs to be instantiated
		if len(depKeysPrev) <= 0 && len(depKeysNext) > 0 {
			componentChanged = true
			actions[instanceKey] = append(actions[instanceKey], component.NewCreateAction(diff.Revision, instanceKey))
		}

		// see if a component needs to be destructed
		if len(depKeysPrev) > 0 && len(depKeysNext) <= 0 {
			actions[instanceKey] = append(actions[instanceKey], component.NewDeleteAction(diff.Revision, instanceKey))
		}

		// see if a component needs to be updated
		if len(depKeysPrev) > 0 && len(depKeysNext) > 0 {
			sameParams := prevInstance.CalculatedCodeParams.DeepEqual(nextInstance.CalculatedCodeParams)
			if !sameParams {
				componentChanged = true

				actions[instanceKey] = appendUpdateAction(actions[instanceKey], updateActions, component.NewUpdateAction(diff.Revision, instanceKey))

				// if it has a parent service, indicate that it basically gets updated as well
				// this is required for adjusting update/creation times of a service with changed component
				// this may produce duplicate "update" actions for the parent service
				if nextInstance.Metadata.Key.IsComponent() {
					serviceKey := nextInstance.Metadata.Key.GetParentServiceKey().GetKey()
					actions[serviceKey] = appendUpdateAction(actions[serviceKey], updateActions, component.NewUpdateAction(diff.Revision, serviceKey))
				}
			}
		}

		// see if a user needs to be detached from a component
		for dependencyID := range depKeysPrev {
			if !depKeysNext[dependencyID] {
				componentChanged = true
				actions[instanceKey] = append(actions[instanceKey], component.NewDetachDependencyAction(diff.Revision, instanceKey, dependencyID))
			}
		}

		// see if a user needs to be attached to a component
		for dependencyID := range depKeysNext {
			if !depKeysPrev[dependencyID] {
				componentChanged = true
				actions[instanceKey] = append(actions[instanceKey], component.NewAttachDependencyAction(diff.Revision, instanceKey, dependencyID))
			}
		}

		if componentChanged {
			endpointsActions = append(endpointsActions, component.NewEndpointsAction(diff.Revision, instanceKey))
		}
	}

	// Generate actions in the right order
	for _, key := range diff.Next.ComponentProcessingOrder {
		actionList, found := actions[key]
		if found {
			diff.Actions = append(diff.Actions, actionList...)
			delete(actions, key)
		}
	}
	for _, key := range diff.Prev.ComponentProcessingOrder {
		actionList, found := actions[key]
		if found {
			diff.Actions = append(diff.Actions, actionList...)
			delete(actions, key)
		}
	}

	diff.Actions = append(diff.Actions, endpointsActions...)

	// call a global post-processing action
	if len(diff.Actions) > 0 {
		diff.Actions = append(diff.Actions, global.NewPostProcessAction(diff.Revision))
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 qemu2

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

	"github.com/docker/machine/libmachine/drivers"
	"k8s.io/minikube/pkg/drivers/qemu"

	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/download"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/registry"
)

const (
	docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/qemu2/"
)

func init() {
	if err := registry.Register(registry.DriverDef{
		Name:     driver.QEMU2,
		Alias:    []string{driver.AliasQEMU},
		Init:     func() drivers.Driver { return qemu.NewDriver("", "") },
		Config:   configure,
		Status:   status,
		Default:  true,
		Priority: registry.Experimental,
	}); err != nil {
		panic(fmt.Sprintf("register failed: %v", err))
	}
}

func qemuSystemProgram() (string, error) {
	arch := runtime.GOARCH
	switch arch {
	case "amd64":
		return "qemu-system-x86_64", nil
	case "arm64":
		return "qemu-system-aarch64", nil
	default:
		return "", fmt.Errorf("unknown arch: %s", arch)
	}
}

func qemuFirmwarePath() (string, error) {
	arch := runtime.GOARCH
	// For macOS, find the correct brew installation path for qemu firmware
	if runtime.GOOS == "darwin" {
		var p, fw string
		switch arch {
		case "amd64":
			p = "/usr/local/Cellar/qemu"
			fw = "share/qemu/edk2-x86_64-code.fd"
		case "arm64":
			p = "/opt/homebrew/Cellar/qemu"
			fw = "share/qemu/edk2-aarch64-code.fd"
		default:
			return "", fmt.Errorf("unknown arch: %s", arch)
		}

		v, err := ioutil.ReadDir(p)
		if err != nil {
			return "", fmt.Errorf("lookup qemu: %v", err)
		}
		for _, version := range v {
			if version.IsDir() {
				return path.Join(p, version.Name(), fw), nil
			}
		}
	}

	switch arch {
	case "amd64":
		return "/usr/share/OVMF/OVMF_CODE.fd", nil
	case "arm64":
		return "/usr/share/AAVMF/AAVMF_CODE.fd", nil
	default:
		return "", fmt.Errorf("unknown arch: %s", arch)
	}
}

func configure(cc config.ClusterConfig, n config.Node) (interface{}, error) {
	name := config.MachineName(cc, n)
	qemuSystem, err := qemuSystemProgram()
	if err != nil {
		return nil, err
	}
	var qemuMachine string
	var qemuCPU string
	switch runtime.GOARCH {
	case "amd64":
		qemuMachine = "" // default
		qemuCPU = ""     // default
	case "arm64":
		qemuMachine = "virt"
		qemuCPU = "cortex-a72"
		// highmem=off needed, see https://patchwork.kernel.org/project/qemu-devel/patch/20201126215017.41156-9-agraf@csgraf.de/#23800615 for details
		if runtime.GOOS == "darwin" {
			qemuMachine += ",highmem=off"
		} else if _, err := os.Stat("/dev/kvm"); err == nil {
			qemuMachine += ",gic-version=3"
			qemuCPU = "host"
		}
	default:
		return nil, fmt.Errorf("unknown arch: %s", runtime.GOARCH)
	}
	qemuFirmware, err := qemuFirmwarePath()
	if err != nil {
		return nil, err
	}
	return qemu.Driver{
		BaseDriver: &drivers.BaseDriver{
			MachineName: name,
			StorePath:   localpath.MiniPath(),
			SSHUser:     "docker",
		},
		Boot2DockerURL: download.LocalISOResource(cc.MinikubeISO),
		DiskSize:       cc.DiskSize,
		Memory:         cc.Memory,
		CPU:            cc.CPUs,
		EnginePort:     2376,
		FirstQuery:     true,
		DiskPath:       filepath.Join(localpath.MiniPath(), "machines", name, fmt.Sprintf("%s.img", name)),
		Program:        qemuSystem,
		BIOS:           runtime.GOARCH != "arm64",
		MachineType:    qemuMachine,
		CPUType:        qemuCPU,
		Firmware:       qemuFirmware,
		VirtioDrives:   false,
		Network:        "user",
		CacheMode:      "default",
		IOMode:         "threads",
	}, nil
}

func status() registry.State {
	qemuSystem, err := qemuSystemProgram()
	if err != nil {
		return registry.State{Error: err, Doc: docURL}
	}

	_, err = exec.LookPath(qemuSystem)
	if err != nil {
		return registry.State{Error: err, Fix: "Install qemu-system", Doc: docURL}
	}

	qemuFirmware, err := qemuFirmwarePath()
	if err != nil {
		return registry.State{Error: err, Doc: docURL}
	}

	if _, err := os.Stat(qemuFirmware); err != nil && runtime.GOARCH == "arm64" {
		return registry.State{Error: err, Fix: "Install uefi firmware", Doc: docURL}
	}

	return registry.State{Installed: true, Healthy: true, Running: true}
}
<commit_msg>revert unrelated change<commit_after>/*
Copyright 2018 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 qemu2

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

	"github.com/docker/machine/libmachine/drivers"
	"k8s.io/minikube/pkg/drivers/qemu"

	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/download"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/registry"
)

const (
	docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/qemu2/"
)

func init() {
	if err := registry.Register(registry.DriverDef{
		Name:     driver.QEMU2,
		Alias:    []string{driver.AliasQEMU},
		Init:     func() drivers.Driver { return qemu.NewDriver("", "") },
		Config:   configure,
		Status:   status,
		Default:  true,
		Priority: registry.Experimental,
	}); err != nil {
		panic(fmt.Sprintf("register failed: %v", err))
	}
}

func qemuSystemProgram() (string, error) {
	arch := runtime.GOARCH
	switch arch {
	case "amd64":
		return "qemu-system-x86_64", nil
	case "arm64":
		return "qemu-system-aarch64", nil
	default:
		return "", fmt.Errorf("unknown arch: %s", arch)
	}
}

func qemuFirmwarePath() (string, error) {
	arch := runtime.GOARCH
	// For macOS, find the correct brew installation path for qemu firmware
	if runtime.GOOS == "darwin" {
		var p, fw string
		switch arch {
		case "amd64":
			p = "/usr/local/Cellar/qemu"
			fw = "share/qemu/edk2-x86_64-code.fd"
		case "arm64":
			p = "/opt/homebrew/Cellar/qemu"
			fw = "share/qemu/edk2-aarch64-code.fd"
		default:
			return "", fmt.Errorf("unknown arch: %s", arch)
		}

		v, err := ioutil.ReadDir(p)
		if err != nil {
			return "", fmt.Errorf("lookup qemu: %v", err)
		}
		for _, version := range v {
			if version.IsDir() {
				return path.Join(p, version.Name(), fw), nil
			}
		}
	}

	switch arch {
	case "amd64":
		return "/usr/share/OVMF/OVMF_CODE.fd", nil
	case "arm64":
		return "/usr/share/AAVMF/AAVMF_CODE.fd", nil
	default:
		return "", fmt.Errorf("unknown arch: %s", arch)
	}
}

func configure(cc config.ClusterConfig, n config.Node) (interface{}, error) {
	name := config.MachineName(cc, n)
	qemuSystem, err := qemuSystemProgram()
	if err != nil {
		return nil, err
	}
	var qemuMachine string
	var qemuCPU string
	switch runtime.GOARCH {
	case "amd64":
		qemuMachine = "" // default
		qemuCPU = ""     // default
	case "arm64":
		qemuMachine = "virt"
		qemuCPU = "cortex-a72"
		// highmem=off needed, see https://patchwork.kernel.org/project/qemu-devel/patch/20201126215017.41156-9-agraf@csgraf.de/#23800615 for details
		if runtime.GOOS == "darwin" {
			qemuMachine = "virt,highmem=off"
		} else if _, err := os.Stat("/dev/kvm"); err == nil {
			qemuMachine = "virt,gic-version=3"
			qemuCPU = "host"
		}
	default:
		return nil, fmt.Errorf("unknown arch: %s", runtime.GOARCH)
	}
	qemuFirmware, err := qemuFirmwarePath()
	if err != nil {
		return nil, err
	}
	return qemu.Driver{
		BaseDriver: &drivers.BaseDriver{
			MachineName: name,
			StorePath:   localpath.MiniPath(),
			SSHUser:     "docker",
		},
		Boot2DockerURL: download.LocalISOResource(cc.MinikubeISO),
		DiskSize:       cc.DiskSize,
		Memory:         cc.Memory,
		CPU:            cc.CPUs,
		EnginePort:     2376,
		FirstQuery:     true,
		DiskPath:       filepath.Join(localpath.MiniPath(), "machines", name, fmt.Sprintf("%s.img", name)),
		Program:        qemuSystem,
		BIOS:           runtime.GOARCH != "arm64",
		MachineType:    qemuMachine,
		CPUType:        qemuCPU,
		Firmware:       qemuFirmware,
		VirtioDrives:   false,
		Network:        "user",
		CacheMode:      "default",
		IOMode:         "threads",
	}, nil
}

func status() registry.State {
	qemuSystem, err := qemuSystemProgram()
	if err != nil {
		return registry.State{Error: err, Doc: docURL}
	}

	_, err = exec.LookPath(qemuSystem)
	if err != nil {
		return registry.State{Error: err, Fix: "Install qemu-system", Doc: docURL}
	}

	qemuFirmware, err := qemuFirmwarePath()
	if err != nil {
		return registry.State{Error: err, Doc: docURL}
	}

	if _, err := os.Stat(qemuFirmware); err != nil && runtime.GOARCH == "arm64" {
		return registry.State{Error: err, Fix: "Install uefi firmware", Doc: docURL}
	}

	return registry.State{Installed: true, Healthy: true, Running: true}
}
<|endoftext|>