<commit_before>package main

import (
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/nictuku/dht"
	nn "github.com/op/go-nanomsg"
)

type BusMessage struct {
	From    string
	To      string
	Seq int64
	Payload []string
}

func NewBusMessage(data []byte, err error) (*BusMessage, error) {
	if err != nil {
		return nil, err
	}

	rv := &BusMessage{}
	if err = json.Unmarshal(data, rv); err != nil {
		return nil, err
	}
	return rv, nil
}

func (b *BusMessage) Bytes() []byte {
	m, err := json.Marshal(b)
	if err != nil {
		log.Fatalf(`can't marshall bus message to json: %s`, err)
	}
	return m
}

type Friend struct {
	name     string
	msg      *BusMessage
	lastSeen time.Time
	mtx      *sync.Mutex
}

func NewFriend(name string) *Friend {
	return &Friend{name: name, mtx: &sync.Mutex{}, lastSeen: time.Now()}
}

func (f *Friend) String() string {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	if f.msg == nil {
		return fmt.Sprintf(`Name: %s, no message, last seen: %s`, f.name, f.lastSeen)
	}

	return fmt.Sprintf("Name: %s, last message: %s (ID: %d), last seen: %s ago", f.name, f.msg.Payload, f.msg.Seq, time.Now().Sub(f.lastSeen))
}

func (f *Friend) HandleMessage(m *BusMessage) {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	if f.msg != nil && f.msg.Seq >= m.Seq {
		/* We've already seen this message */
		return
	}

	f.msg = m
	f.lastSeen = time.Now()

	log.Printf(`tgt: %s, src: %s, msg: %v`, m.To, m.From, m.Payload)
}

type PeerManager struct {
	d          *dht.DHT
	bus        *nn.BusSocket
	nick       string
	friends    map[string]*Friend
	oldfriends map[string]*Friend
	venue      string
	sequence   int64
	mtx        *sync.RWMutex
}

func NewPeerManager() *PeerManager {
	conf := dht.NewConfig()
	conf.Port = 55000
	conf.CleanupPeriod = 3 * time.Second

	pm := PeerManager{}

	var err error
	pm.bus, err = nn.NewBusSocket()
	if err != nil {
		log.Fatalf(`can't create BUS socket: %s`, err)
	}

	if pm.d, err = dht.New(conf); err != nil {
		log.Fatalf(`can't create DHT: %s`, err)
	}

	sum := sha1.Sum([]byte("LetsMeetHere"))
	pm.venue = hex.EncodeToString(sum[:])

	buf := make([]byte, 4)
	rand.Read(buf)
	pm.nick = hex.EncodeToString(buf)

	log.Printf(`My nickname is %s`, pm.nick)
	log.Printf(`I will meet my friends at %s`, pm.venue)

	pm.friends = make(map[string]*Friend)
	pm.oldfriends = make(map[string]*Friend)

	pm.mtx = &sync.RWMutex{}

	return &pm
}

func (pm *PeerManager) String() string {
	pm.mtx.RLock()
	defer pm.mtx.RUnlock()

	s := []string{
		fmt.Sprintf("DHT: Port: %d", pm.d.Port()),
		fmt.Sprintf("\r\nI am %s", pm.nick),
		fmt.Sprintf("I have %d friend(s)", len(pm.friends)),
		fmt.Sprintf("We're meeting at '%s'", pm.venue),
		"\r\nThese are my friends:\r\n",
	}

	for _, f := range pm.friends {
		s = append(s, f.String())
	}

	s = append(s, "\r\nI haven't heard from these guys in a while:\r\n")
	for _, f := range pm.oldfriends {
		s = append(s, f.String())
	}

	return strings.Join(s, "\r\n")
}

func (pm *PeerManager) Publish(dst string, payload []string) error {
	pm.mtx.Lock()
	pm.sequence++
	m := &BusMessage{From: pm.nick, To: dst, Seq: pm.sequence, Payload: payload}
	pm.mtx.Unlock()

	_, err := pm.bus.Send(m.Bytes(), 0)
	return err
}

func (pm *PeerManager) Loop() {
	ih, err := dht.DecodeInfoHash(pm.venue)
	if err != nil {
		log.Printf(`can't decode infohash: %s`, err)
		return
	}

	if err := pm.d.Start(); err != nil {
		log.Printf(`can't start DHT: %s`, err)
		return
	}

	log.Printf(`DHT bound to port %d`, pm.d.Port())

	ep, err := pm.bus.Bind(fmt.Sprintf("tcp://*:%d", pm.d.Port()))
	if err != nil {
		log.Fatalf(`can't bind BUS socket: %s`, err)
	}
	log.Printf(`BUS endpoint: %s`, ep)

	go pm.drainPeers()

	go func() {
		for {
			time.Sleep(2 * time.Second)
			pm.mtx.Lock()
			for name, f := range pm.friends {
				f.mtx.Lock()
				if f.lastSeen.Add(10 * time.Second).Before(time.Now()) {
					log.Printf(`haven't heard from %s in a while`, name)
					pm.oldfriends[name] = f
					delete(pm.friends, name)
				}
				f.mtx.Unlock()
			}
			pm.mtx.Unlock()
		}
	}()

	go func() {
		for {
			/* Receive message */
			m, err := NewBusMessage(pm.bus.Recv(0))
			if err != nil {
				log.Printf(`can't receive message from bus: %s`, err)
				continue
			}

			/* Ignore messages from ourselves */
			if m.From == pm.nick {
				continue
			}

			/* Ignore messages not to 'everyone' or us */
			if m.To != pm.nick && m.To != "*" {
				continue
			}

			pm.mtx.Lock()
			f, ok := pm.friends[m.From]
			if !ok {
				f = NewFriend(m.From)
				pm.friends[m.From] = f
			}
			pm.mtx.Unlock()

			f.HandleMessage(m)

			if !ok {
				log.Printf(`%s is a new friend!`, m.From)
				pm.Publish(m.From, []string{"hello friend :)"})
			}
		}
	}()

	go func() {
		i := 0
		for {
			pm.Publish("*", []string{"I'm alive", fmt.Sprintf("%d", i)})
			i++
			time.Sleep(5 * time.Second)
		}
	}()

	for {
		pm.d.PeersRequest(string(ih), true)
		time.Sleep(5 * time.Second)
	}
}

func (pm *PeerManager) drainPeers() {
	log.Printf(`draining DHT`)
	seen := make(map[string]struct{})

	for r := range pm.d.PeersRequestResults {
		for _, peers := range r {
			for _, x := range peers {
				addr := dht.DecodePeerAddress(x)
				if _, ok := seen[addr]; !ok {
					pm.connectToPeer(addr)
				}
				seen[addr] = struct{}{}
			}
		}
	}
}

func (pm *PeerManager) connectToPeer(addr string) {
	if _, err := pm.bus.Connect(fmt.Sprintf("tcp://%s", addr)); err != nil {
		log.Printf(`can't connect SUB to %s: %s`, addr, err)
	}
}
<commit_msg>Print sequence ID in debugger view<commit_after>package main

import (
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/nictuku/dht"
	nn "github.com/op/go-nanomsg"
)

type BusMessage struct {
	From    string
	To      string
	Seq int64
	Payload []string
}

func NewBusMessage(data []byte, err error) (*BusMessage, error) {
	if err != nil {
		return nil, err
	}

	rv := &BusMessage{}
	if err = json.Unmarshal(data, rv); err != nil {
		return nil, err
	}
	return rv, nil
}

func (b *BusMessage) Bytes() []byte {
	m, err := json.Marshal(b)
	if err != nil {
		log.Fatalf(`can't marshall bus message to json: %s`, err)
	}
	return m
}

type Friend struct {
	name     string
	msg      *BusMessage
	lastSeen time.Time
	mtx      *sync.Mutex
}

func NewFriend(name string) *Friend {
	return &Friend{name: name, mtx: &sync.Mutex{}, lastSeen: time.Now()}
}

func (f *Friend) String() string {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	if f.msg == nil {
		return fmt.Sprintf(`Name: %s, no message, last seen: %s`, f.name, f.lastSeen)
	}

	return fmt.Sprintf("Name: %s, last message: %s (ID: %d), last seen: %s ago", f.name, f.msg.Payload, f.msg.Seq, time.Now().Sub(f.lastSeen))
}

func (f *Friend) HandleMessage(m *BusMessage) {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	if f.msg != nil && f.msg.Seq >= m.Seq {
		/* We've already seen this message */
		return
	}

	f.msg = m
	f.lastSeen = time.Now()

	log.Printf(`tgt: %s, src: %s, msg: %v`, m.To, m.From, m.Payload)
}

type PeerManager struct {
	d          *dht.DHT
	bus        *nn.BusSocket
	nick       string
	friends    map[string]*Friend
	oldfriends map[string]*Friend
	venue      string
	sequence   int64
	mtx        *sync.RWMutex
}

func NewPeerManager() *PeerManager {
	conf := dht.NewConfig()
	conf.Port = 55000
	conf.CleanupPeriod = 3 * time.Second

	pm := PeerManager{}

	var err error
	pm.bus, err = nn.NewBusSocket()
	if err != nil {
		log.Fatalf(`can't create BUS socket: %s`, err)
	}

	if pm.d, err = dht.New(conf); err != nil {
		log.Fatalf(`can't create DHT: %s`, err)
	}

	sum := sha1.Sum([]byte("LetsMeetHere"))
	pm.venue = hex.EncodeToString(sum[:])

	buf := make([]byte, 4)
	rand.Read(buf)
	pm.nick = hex.EncodeToString(buf)

	log.Printf(`My nickname is %s`, pm.nick)
	log.Printf(`I will meet my friends at %s`, pm.venue)

	pm.friends = make(map[string]*Friend)
	pm.oldfriends = make(map[string]*Friend)

	pm.mtx = &sync.RWMutex{}

	return &pm
}

func (pm *PeerManager) String() string {
	pm.mtx.RLock()
	defer pm.mtx.RUnlock()

	s := []string{
		fmt.Sprintf("DHT: Port: %d", pm.d.Port()),
		fmt.Sprintf("\r\nI am %s, my sequence ID is %d", pm.nick, pm.sequence),
		fmt.Sprintf("I have %d friend(s)", len(pm.friends)),
		fmt.Sprintf("We're meeting at '%s'", pm.venue),
		"\r\nThese are my friends:\r\n",
	}

	for _, f := range pm.friends {
		s = append(s, f.String())
	}

	s = append(s, "\r\nI haven't heard from these guys in a while:\r\n")
	for _, f := range pm.oldfriends {
		s = append(s, f.String())
	}

	return strings.Join(s, "\r\n")
}

func (pm *PeerManager) Publish(dst string, payload []string) error {
	pm.mtx.Lock()
	pm.sequence++
	m := &BusMessage{From: pm.nick, To: dst, Seq: pm.sequence, Payload: payload}
	pm.mtx.Unlock()

	_, err := pm.bus.Send(m.Bytes(), 0)
	return err
}

func (pm *PeerManager) Loop() {
	ih, err := dht.DecodeInfoHash(pm.venue)
	if err != nil {
		log.Printf(`can't decode infohash: %s`, err)
		return
	}

	if err := pm.d.Start(); err != nil {
		log.Printf(`can't start DHT: %s`, err)
		return
	}

	log.Printf(`DHT bound to port %d`, pm.d.Port())

	ep, err := pm.bus.Bind(fmt.Sprintf("tcp://*:%d", pm.d.Port()))
	if err != nil {
		log.Fatalf(`can't bind BUS socket: %s`, err)
	}
	log.Printf(`BUS endpoint: %s`, ep)

	go pm.drainPeers()

	go func() {
		for {
			time.Sleep(2 * time.Second)
			pm.mtx.Lock()
			for name, f := range pm.friends {
				f.mtx.Lock()
				if f.lastSeen.Add(10 * time.Second).Before(time.Now()) {
					log.Printf(`haven't heard from %s in a while`, name)
					pm.oldfriends[name] = f
					delete(pm.friends, name)
				}
				f.mtx.Unlock()
			}
			pm.mtx.Unlock()
		}
	}()

	go func() {
		for {
			/* Receive message */
			m, err := NewBusMessage(pm.bus.Recv(0))
			if err != nil {
				log.Printf(`can't receive message from bus: %s`, err)
				continue
			}

			/* Ignore messages from ourselves */
			if m.From == pm.nick {
				continue
			}

			/* Ignore messages not to 'everyone' or us */
			if m.To != pm.nick && m.To != "*" {
				continue
			}

			pm.mtx.Lock()
			f, ok := pm.friends[m.From]
			if !ok {
				f = NewFriend(m.From)
				pm.friends[m.From] = f
			}
			pm.mtx.Unlock()

			f.HandleMessage(m)

			if !ok {
				log.Printf(`%s is a new friend!`, m.From)
				pm.Publish(m.From, []string{"hello friend :)"})
			}
		}
	}()

	go func() {
		i := 0
		for {
			pm.Publish("*", []string{"I'm alive", fmt.Sprintf("%d", i)})
			i++
			time.Sleep(5 * time.Second)
		}
	}()

	for {
		pm.d.PeersRequest(string(ih), true)
		time.Sleep(5 * time.Second)
	}
}

func (pm *PeerManager) drainPeers() {
	log.Printf(`draining DHT`)
	seen := make(map[string]struct{})

	for r := range pm.d.PeersRequestResults {
		for _, peers := range r {
			for _, x := range peers {
				addr := dht.DecodePeerAddress(x)
				if _, ok := seen[addr]; !ok {
					pm.connectToPeer(addr)
				}
				seen[addr] = struct{}{}
			}
		}
	}
}

func (pm *PeerManager) connectToPeer(addr string) {
	if _, err := pm.bus.Connect(fmt.Sprintf("tcp://%s", addr)); err != nil {
		log.Printf(`can't connect SUB to %s: %s`, addr, err)
	}
}
<|endoftext|>


<commit_before>package storage

import (
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
	log "github.com/golang/glog"
	"github.com/jrupac/goliath/models"
	_ "github.com/lib/pq"
)

const (
	DIALECT               = "postgres"
	FOLDER_TABLE          = "Folder"
	FOLDER_CHILDREN_TABLE = "FolderChildren"
	FEED_TABLE            = "Feed"
	ARTICLE_TABLE         = "Article"
)

type Database struct {
	db *sql.DB
}

func Open(dbPath string) (*Database, error) {
	db := new(Database)

	d, err := sql.Open(DIALECT, dbPath)
	if err != nil {
		return nil, err
	}

	db.db = d
	return db, nil
}

func (d *Database) Close() error {
	return d.db.Close()
}

func (d *Database) InsertArticle(a models.Article) error {
	var articleId int64
	var count int
	err := d.db.QueryRow(
		`SELECT COUNT(*) FROM `+ARTICLE_TABLE+` WHERE hash = $1`, a.Hash()).Scan(&count)
	if err != nil {
		return err
	}
	if count > 0 {
		log.Infof("Duplicate article entry, skipping.")
		return nil
	}

	err = d.db.QueryRow(
		`INSERT INTO `+ARTICLE_TABLE+`
		(feed, folder, hash, title, summary, content, link, date, read)
		VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`,
		a.FeedId, a.FolderId, a.Hash(), a.Title, a.Summary, a.Content, a.Link, a.Date, a.Read).Scan(&articleId)
	if err != nil {
		return err
	}
	a.Id = articleId
	return nil
}

func (d *Database) InsertFeedIcon(feedId int64, img []byte) error {
	var count int
	err := d.db.QueryRow(
		`SELECT COUNT(*) FROM `+FEED_TABLE+` WHERE id = $1`, feedId).Scan(&count)
	if err != nil {
		return err
	}
	if count == 0 {
		return errors.New(fmt.Sprintf("Original feed not in table: %d", feedId))
	}

	// Convert into a CockroachDB-specific hex string for insertion
	dst := make([]byte, hex.DecodedLen(len(img)))
	n, err := hex.Decode(dst, img)
	if err != nil {
		return err
	}
	h := fmt.Sprintf("x'%s'", dst[:n])

	_, err = d.db.Query(`INSERT INTO `+FEED_TABLE+` (id, icon) VALUES ($1, $2)`, feedId, h)
	return err
}

func (d *Database) GetAllFeeds() ([]models.Feed, error) {
	feeds := []models.Feed{}
	rows, err := d.db.Query(`SELECT id, folder, title, description, url, text FROM ` + FEED_TABLE)
	if err != nil {
		return feeds, err
	}
	defer rows.Close()

	for rows.Next() {
		f := models.Feed{}
		if err = rows.Scan(&f.Id, &f.FolderId, &f.Title, &f.Description, &f.Url, &f.Text); err != nil {
			return feeds, err
		}
		feeds = append(feeds, f)
	}

	return feeds, err
}

func (d *Database) ImportOpml(opml *models.Opml) error {
	root := opml.Folders
	var rootId int64
	err := d.db.QueryRow(
		`INSERT INTO `+FOLDER_TABLE+`(name) VALUES($1) RETURNING id`, root.Name).Scan(&rootId)
	if err != nil {
		return err
	}
	root.Id = rootId
	return d.importChildren(root)
}

func (d *Database) importChildren(parent models.Folder) error {
	var err error
	var feedId int64
	var count int
	for _, f := range parent.Feed {
		err := d.db.QueryRow(
			`SELECT COUNT(*) FROM `+FEED_TABLE+` WHERE hash = $1`, f.Hash()).Scan(&count)
		if err != nil {
			return err
		}
		if count > 0 {
			log.Infof("Duplicate feed entry, skipping.")
			continue
		}

		err = d.db.QueryRow(
			`INSERT INTO `+FEED_TABLE+`(folder, hash, title, description, url, text)
			VALUES($1, $2, $3, $4, $5, $6) RETURNING id`,
			parent.Id, f.Hash(), f.Title, f.Description, f.Url, f.Text).Scan(&feedId)
		if err != nil {
			return err
		}
		f.Id = feedId
		f.FolderId = parent.Id
	}

	var childId int64
	for _, child := range parent.Folders {
		err = d.db.QueryRow(
			`INSERT INTO `+FOLDER_TABLE+`(name) VALUES($1) RETURNING id`, child.Name).Scan(&childId)
		if err != nil {
			return err
		}
		child.Id = childId
		_, err = d.db.Query(
			`INSERT INTO `+FOLDER_CHILDREN_TABLE+`(parent, child) VALUES($1, $2)`, parent.Id, child.Id)
		if err != nil {
			return err
		}
		if err = d.importChildren(child); err != nil {
			return err
		}
	}
	return err
}
<commit_msg>Simplify feed icon insertion.<commit_after>package storage

import (
	"database/sql"
	"encoding/hex"
	"errors"
	"fmt"
	log "github.com/golang/glog"
	"github.com/jrupac/goliath/models"
	_ "github.com/lib/pq"
)

const (
	DIALECT               = "postgres"
	FOLDER_TABLE          = "Folder"
	FOLDER_CHILDREN_TABLE = "FolderChildren"
	FEED_TABLE            = "Feed"
	ARTICLE_TABLE         = "Article"
)

type Database struct {
	db *sql.DB
}

func Open(dbPath string) (*Database, error) {
	db := new(Database)

	d, err := sql.Open(DIALECT, dbPath)
	if err != nil {
		return nil, err
	}

	db.db = d
	return db, nil
}

func (d *Database) Close() error {
	return d.db.Close()
}

func (d *Database) InsertArticle(a models.Article) error {
	var articleId int64
	var count int
	err := d.db.QueryRow(
		`SELECT COUNT(*) FROM `+ARTICLE_TABLE+` WHERE hash = $1`, a.Hash()).Scan(&count)
	if err != nil {
		return err
	}
	if count > 0 {
		log.Infof("Duplicate article entry, skipping.")
		return nil
	}

	err = d.db.QueryRow(
		`INSERT INTO `+ARTICLE_TABLE+`
		(feed, folder, hash, title, summary, content, link, date, read)
		VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id`,
		a.FeedId, a.FolderId, a.Hash(), a.Title, a.Summary, a.Content, a.Link, a.Date, a.Read).Scan(&articleId)
	if err != nil {
		return err
	}
	a.Id = articleId
	return nil
}

func (d *Database) InsertFeedIcon(feedId int64, img []byte) error {
	var count int
	err := d.db.QueryRow(
		`SELECT COUNT(*) FROM `+FEED_TABLE+` WHERE id = $1`, feedId).Scan(&count)
	if err != nil {
		return err
	}
	if count == 0 {
		return errors.New(fmt.Sprintf("Original feed not in table: %d", feedId))
	}

	// Convert into a CockroachDB-specific hex string for insertion
	h := fmt.Sprintf("x'%s'", hex.EncodeToString(img))

	_, err = d.db.Query(`UPDATE `+FEED_TABLE+` SET icon = $1  WHERE id = $2`, h, feedId)
	return err
}

func (d *Database) GetAllFeeds() ([]models.Feed, error) {
	feeds := []models.Feed{}
	rows, err := d.db.Query(`SELECT id, folder, title, description, url, text FROM ` + FEED_TABLE)
	if err != nil {
		return feeds, err
	}
	defer rows.Close()

	for rows.Next() {
		f := models.Feed{}
		if err = rows.Scan(&f.Id, &f.FolderId, &f.Title, &f.Description, &f.Url, &f.Text); err != nil {
			return feeds, err
		}
		feeds = append(feeds, f)
	}

	return feeds, err
}

func (d *Database) ImportOpml(opml *models.Opml) error {
	root := opml.Folders
	var rootId int64
	err := d.db.QueryRow(
		`INSERT INTO `+FOLDER_TABLE+`(name) VALUES($1) RETURNING id`, root.Name).Scan(&rootId)
	if err != nil {
		return err
	}
	root.Id = rootId
	return d.importChildren(root)
}

func (d *Database) importChildren(parent models.Folder) error {
	var err error
	var feedId int64
	var count int
	for _, f := range parent.Feed {
		err := d.db.QueryRow(
			`SELECT COUNT(*) FROM `+FEED_TABLE+` WHERE hash = $1`, f.Hash()).Scan(&count)
		if err != nil {
			return err
		}
		if count > 0 {
			log.Infof("Duplicate feed entry, skipping.")
			continue
		}

		err = d.db.QueryRow(
			`INSERT INTO `+FEED_TABLE+`(folder, hash, title, description, url, text)
			VALUES($1, $2, $3, $4, $5, $6) RETURNING id`,
			parent.Id, f.Hash(), f.Title, f.Description, f.Url, f.Text).Scan(&feedId)
		if err != nil {
			return err
		}
		f.Id = feedId
		f.FolderId = parent.Id
	}

	var childId int64
	for _, child := range parent.Folders {
		err = d.db.QueryRow(
			`INSERT INTO `+FOLDER_TABLE+`(name) VALUES($1) RETURNING id`, child.Name).Scan(&childId)
		if err != nil {
			return err
		}
		child.Id = childId
		_, err = d.db.Query(
			`INSERT INTO `+FOLDER_CHILDREN_TABLE+`(parent, child) VALUES($1, $2)`, parent.Id, child.Id)
		if err != nil {
			return err
		}
		if err = d.importChildren(child); err != nil {
			return err
		}
	}
	return err
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"k8s.io/klog"
	"net/http"
	"os"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	certutil "k8s.io/client-go/util/cert"

	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/vn-agent/certificate"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/vn-agent/config"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/vn-agent/server"
)

func main() {
	var cfg config.Flags

	cmd := NewCommand(cfg)

	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

// NewCommand creates a new top-level command.
func NewCommand(c config.Flags) *cobra.Command {
	cmd := &cobra.Command{
		Use: "vn-agent",
		RunE: func(cmd *cobra.Command, args []string) error {
			return run(&c)
		},
	}

	installFlags(cmd.Flags(), &c)
	return cmd
}

func installFlags(flags *pflag.FlagSet, c *config.Flags) {
	flags.StringVar(&c.ClientCAFile, "client-ca-file", c.ClientCAFile, "kube config file to use for connecting to the Kubernetes API server")
	flags.StringVar(&c.CertDirectory, "cert-dir", c.CertDirectory, "CertDirectory is the directory where the TLS certs are located")
	flags.StringVar(&c.TLSCertFile, "tls-cert-file", c.TLSCertFile, "TLSCertFile is the file containing x509 Certificate for HTTPS")
	flags.StringVar(&c.TLSPrivateKeyFile, "tls-private-key-file", c.TLSPrivateKeyFile, "TLSPrivateKeyFile is the file containing x509 private key matching tlsCertFile")
	flags.UintVar(&c.Port, "port", 10550, "Port is the server listen on")

	flags.StringVar(&c.KubeletConfig.CertFile, "kubelet-client-certificate", c.KubeletConfig.CertFile, "Path to a client cert file for TLS")
	flags.StringVar(&c.KubeletConfig.KeyFile, "kubelet-client-key", c.KubeletConfig.KeyFile, "Path to a client key file for TLS")
	flags.UintVar(&c.KubeletConfig.Port, "kubelet-port", 10250, "Kubelet security port")
}

func run(c *config.Flags) error {
	kubeletClientCertPair, err := tls.LoadX509KeyPair(c.KubeletConfig.CertFile, c.KubeletConfig.KeyFile)
	if err != nil {
		return errors.Wrapf(err, "failed to load kubelet tls config")
	}

	handler, err := server.NewServer(&config.Config{
		KubeletClientCert: kubeletClientCertPair,
		KubeletServerHost: fmt.Sprintf("127.0.0.1:%v", c.KubeletConfig.Port),
	})
	if err != nil {
		return errors.Wrapf(err, "create server")
	}

	clientCAs, err := certutil.CertsFromFile(c.ClientCAFile)
	if err != nil {
		return errors.Wrapf(err, "unable to load client CA file")
	}

	certPool := x509.NewCertPool()
	for _, cert := range clientCAs {
		certPool.AddCert(cert)
	}

	s := &http.Server{
		Addr:    fmt.Sprintf("localhost:%d", c.Port),
		Handler: handler,
		TLSConfig: &tls.Config{
			ClientCAs:  certPool,
			ClientAuth: tls.RequireAndVerifyClientCert,
		},
	}

	tlsConfig, err := certificate.InitializeTLS(c)
	if err != nil {
		return errors.Wrapf(err, "failed to initial tls config")
	}

	klog.Infof("server listen on %s", s.Addr)
	klog.Infof("config %+v", c)
	klog.Fatal(s.ListenAndServeTLS(tlsConfig.CertFile, tlsConfig.KeyFile))

	return nil
}
<commit_msg>vn-agent: make client ca optional<commit_after>/*
Copyright 2019 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 main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"k8s.io/klog"
	"net/http"
	"os"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	certutil "k8s.io/client-go/util/cert"
	"k8s.io/component-base/logs"

	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/vn-agent/certificate"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/vn-agent/config"
	"github.com/kubernetes-sigs/multi-tenancy/incubator/virtualcluster/pkg/vn-agent/server"
)

func main() {
	var cfg config.Flags

	cmd := NewCommand(cfg)

	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "%v\n", err)
		os.Exit(1)
	}
}

// NewCommand creates a new top-level command.
func NewCommand(c config.Flags) *cobra.Command {
	cmd := &cobra.Command{
		Use: "vn-agent",
		RunE: func(cmd *cobra.Command, args []string) error {
			return run(&c)
		},
	}

	installFlags(cmd.Flags(), &c)
	return cmd
}

func installFlags(flags *pflag.FlagSet, c *config.Flags) {
	flags.StringVar(&c.ClientCAFile, "client-ca-file", c.ClientCAFile, "kube config file to use for connecting to the Kubernetes API server")
	flags.StringVar(&c.CertDirectory, "cert-dir", c.CertDirectory, "CertDirectory is the directory where the TLS certs are located")
	flags.StringVar(&c.TLSCertFile, "tls-cert-file", c.TLSCertFile, "TLSCertFile is the file containing x509 Certificate for HTTPS")
	flags.StringVar(&c.TLSPrivateKeyFile, "tls-private-key-file", c.TLSPrivateKeyFile, "TLSPrivateKeyFile is the file containing x509 private key matching tlsCertFile")
	flags.UintVar(&c.Port, "port", 10550, "Port is the server listen on")

	flags.StringVar(&c.KubeletConfig.CertFile, "kubelet-client-certificate", c.KubeletConfig.CertFile, "Path to a client cert file for TLS")
	flags.StringVar(&c.KubeletConfig.KeyFile, "kubelet-client-key", c.KubeletConfig.KeyFile, "Path to a client key file for TLS")
	flags.UintVar(&c.KubeletConfig.Port, "kubelet-port", 10250, "Kubelet security port")
}

func run(c *config.Flags) error {
	kubeletClientCertPair, err := tls.LoadX509KeyPair(c.KubeletConfig.CertFile, c.KubeletConfig.KeyFile)
	if err != nil {
		return errors.Wrapf(err, "failed to load kubelet tls config")
	}

	handler, err := server.NewServer(&config.Config{
		KubeletClientCert: kubeletClientCertPair,
		KubeletServerHost: fmt.Sprintf("https://127.0.0.1:%v", c.KubeletConfig.Port),
	})
	if err != nil {
		return errors.Wrapf(err, "create server")
	}

	s := &http.Server{
		Addr:    fmt.Sprintf(":%d", c.Port),
		Handler: handler,
		TLSConfig: &tls.Config{
			ClientAuth: tls.RequestClientCert,
		},
	}

	if c.ClientCAFile != "" {
		clientCAs, err := certutil.CertsFromFile(c.ClientCAFile)
		if err != nil {
			return errors.Wrapf(err, "unable to load client CA file")
		}

		certPool := x509.NewCertPool()
		for _, cert := range clientCAs {
			certPool.AddCert(cert)
		}

		s.TLSConfig.ClientCAs = certPool
		s.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
	}

	tlsConfig, err := certificate.InitializeTLS(c)
	if err != nil {
		return errors.Wrapf(err, "failed to initial tls config")
	}

	logs.InitLogs()
	defer logs.FlushLogs()

	klog.Infof("server listen on %s", s.Addr)
	klog.Infof("config %+v", c)
	klog.Fatal(s.ListenAndServeTLS(tlsConfig.CertFile, tlsConfig.KeyFile))

	return nil
}
<|endoftext|>


<commit_before>package structhash

import (
	"bytes"
	"crypto/md5"
	"crypto/sha1"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

// Version returns the version of the supplied hash as an integer
// or -1 on failure
func Version(h string) int {
	if h == "" {
		return -1
	}
	if h[0] != 'v' {
		return -1
	}
	if spos := strings.IndexRune(h[1:], '_'); spos >= 0 {
		n, e := strconv.Atoi(h[1 : spos+1])
		if e != nil {
			return -1
		}
		return n
	}
	return -1
}

// Hash takes a data structure and returns a hash string of that data structure
// at the version asked.
//
// This function uses md5 hashing function and default formatter. See also Dump()
// function.
func Hash(c interface{}, version int) (string, error) {
	return fmt.Sprintf("v%d_%x", version, Md5(c, version)), nil
}

// Dump takes a data structure and returns its byte representation. This can be
// useful if you need to use your own hashing function or formatter.
func Dump(c interface{}, version int) []byte {
	return serialize(c, version)
}

// Md5 takes a data structure and returns its md5 hash.
// This is a shorthand for md5.Sum(Dump(c, version)).
func Md5(c interface{}, version int) []byte {
	sum := md5.Sum(Dump(c, version))
	return sum[:]
}

// Sha1 takes a data structure and returns its sha1 hash.
// This is a shorthand for sha1.Sum(Dump(c, version)).
func Sha1(c interface{}, version int) []byte {
	sum := sha1.Sum(Dump(c, version))
	return sum[:]
}

type item struct {
	name  string
	value reflect.Value
}

type itemSorter []item

func (s itemSorter) Len() int {
	return len(s)
}

func (s itemSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s itemSorter) Less(i, j int) bool {
	return s[i].name < s[j].name
}

type structFieldFilter func(reflect.StructField) (string, bool)

func writeValue(buf *bytes.Buffer, val reflect.Value, fltr structFieldFilter) {
	switch val.Kind() {
	case reflect.String:
		buf.WriteByte('"')
		buf.WriteString(val.String())
		buf.WriteByte('"')
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		buf.WriteString(strconv.FormatInt(val.Int(), 10))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		buf.WriteString(strconv.FormatUint(val.Uint(), 10))
	case reflect.Float32, reflect.Float64:
		buf.WriteString(strconv.FormatFloat(val.Float(), 'E', -1, 64))
	case reflect.Bool:
		if val.Bool() {
			buf.WriteByte('t')
		} else {
			buf.WriteByte('f')
		}
	case reflect.Ptr:
		if !val.IsNil() || val.Type().Elem().Kind() == reflect.Struct {
			writeValue(buf, reflect.Indirect(val), fltr)
		} else {
			writeValue(buf, reflect.Zero(val.Type().Elem()), fltr)
		}
	case reflect.Array, reflect.Slice:
		buf.WriteByte('[')
		len := val.Len()
		for i := 0; i < len; i++ {
			if i != 0 {
				buf.WriteByte(',')
			}
			writeValue(buf, val.Index(i), fltr)
		}
		buf.WriteByte(']')
	case reflect.Map:
		mk := val.MapKeys()
		items := make([]item, len(mk), len(mk))
		// Get all values
		for i, _ := range items {
			items[i].name = formatValue(mk[i], fltr)
			items[i].value = val.MapIndex(mk[i])
		}

		// Sort values by key
		sort.Sort(itemSorter(items))

		buf.WriteByte('[')
		for i, _ := range items {
			if i != 0 {
				buf.WriteByte(',')
			}
			buf.WriteString(items[i].name)
			buf.WriteByte(':')
			writeValue(buf, items[i].value, fltr)
		}
		buf.WriteByte(']')
	case reflect.Struct:
		vtype := val.Type()
		flen := vtype.NumField()
		items := make([]item, 0, flen)
		// Get all fields
		for i := 0; i < flen; i++ {
			field := vtype.Field(i)
			it := item{field.Name, val.Field(i)}
			if fltr != nil {
				if name, ok := fltr(field); ok {
					it.name = name
				} else {
					continue
				}
			}
			items = append(items, it)
		}
		// Sort fields by name
		sort.Sort(itemSorter(items))

		buf.WriteByte('{')
		for i, _ := range items {
			if i != 0 {
				buf.WriteByte(',')
			}
			buf.WriteString(items[i].name)
			buf.WriteByte(':')
			writeValue(buf, items[i].value, fltr)
		}
		buf.WriteByte('}')
	case reflect.Interface:
		writeValue(buf, reflect.ValueOf(val.Interface()), fltr)
	default:
		buf.WriteString(val.String())
	}
}

func formatValue(val reflect.Value, fltr structFieldFilter) string {
	if val.Kind() == reflect.String {
		return "\"" + val.String() + "\""
	}

	var buf bytes.Buffer
	writeValue(&buf, val, fltr)

	return string(buf.Bytes())
}

func filterField(f reflect.StructField, version int) (string, bool) {
	var err error
	name := f.Name
	ver := 0
	lastver := -1
	if str := f.Tag.Get("hash"); str != "" {
		if str == "-" {
			return "", false
		}
		for _, tag := range strings.Split(str, " ") {
			args := strings.Split(strings.TrimSpace(tag), ":")
			if len(args) != 2 {
				return "", false
			}
			switch args[0] {
			case "name":
				name = args[1]
			case "version":
				if ver, err = strconv.Atoi(args[1]); err != nil {
					return "", false
				}
			case "lastversion":
				if lastver, err = strconv.Atoi(args[1]); err != nil {
					return "", false
				}
			}
		}
	} else {
		if str := f.Tag.Get("lastversion"); str != "" {
			if lastver, err = strconv.Atoi(str); err != nil {
				return "", false
			}
		}
		if str := f.Tag.Get("version"); str != "" {
			if ver, err = strconv.Atoi(str); err != nil {
				return "", false
			}
		}
	}
	if lastver != -1 && lastver < version {
		return "", false
	}
	if ver > version {
		return "", false
	}
	return name, true
}

func serialize(object interface{}, version int) []byte {
	var buf bytes.Buffer

	writeValue(&buf, reflect.ValueOf(object),
		func(f reflect.StructField) (string, bool) {
			return filterField(f, version)
		})

	return buf.Bytes()
}
<commit_msg>Modify dumped item in filter function instead of returning new name<commit_after>package structhash

import (
	"bytes"
	"crypto/md5"
	"crypto/sha1"
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"
)

// Version returns the version of the supplied hash as an integer
// or -1 on failure
func Version(h string) int {
	if h == "" {
		return -1
	}
	if h[0] != 'v' {
		return -1
	}
	if spos := strings.IndexRune(h[1:], '_'); spos >= 0 {
		n, e := strconv.Atoi(h[1 : spos+1])
		if e != nil {
			return -1
		}
		return n
	}
	return -1
}

// Hash takes a data structure and returns a hash string of that data structure
// at the version asked.
//
// This function uses md5 hashing function and default formatter. See also Dump()
// function.
func Hash(c interface{}, version int) (string, error) {
	return fmt.Sprintf("v%d_%x", version, Md5(c, version)), nil
}

// Dump takes a data structure and returns its byte representation. This can be
// useful if you need to use your own hashing function or formatter.
func Dump(c interface{}, version int) []byte {
	return serialize(c, version)
}

// Md5 takes a data structure and returns its md5 hash.
// This is a shorthand for md5.Sum(Dump(c, version)).
func Md5(c interface{}, version int) []byte {
	sum := md5.Sum(Dump(c, version))
	return sum[:]
}

// Sha1 takes a data structure and returns its sha1 hash.
// This is a shorthand for sha1.Sum(Dump(c, version)).
func Sha1(c interface{}, version int) []byte {
	sum := sha1.Sum(Dump(c, version))
	return sum[:]
}

type item struct {
	name  string
	value reflect.Value
}

type itemSorter []item

func (s itemSorter) Len() int {
	return len(s)
}

func (s itemSorter) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s itemSorter) Less(i, j int) bool {
	return s[i].name < s[j].name
}

type structFieldFilter func(reflect.StructField, *item) bool

func writeValue(buf *bytes.Buffer, val reflect.Value, fltr structFieldFilter) {
	switch val.Kind() {
	case reflect.String:
		buf.WriteByte('"')
		buf.WriteString(val.String())
		buf.WriteByte('"')
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		buf.WriteString(strconv.FormatInt(val.Int(), 10))
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		buf.WriteString(strconv.FormatUint(val.Uint(), 10))
	case reflect.Float32, reflect.Float64:
		buf.WriteString(strconv.FormatFloat(val.Float(), 'E', -1, 64))
	case reflect.Bool:
		if val.Bool() {
			buf.WriteByte('t')
		} else {
			buf.WriteByte('f')
		}
	case reflect.Ptr:
		if !val.IsNil() || val.Type().Elem().Kind() == reflect.Struct {
			writeValue(buf, reflect.Indirect(val), fltr)
		} else {
			writeValue(buf, reflect.Zero(val.Type().Elem()), fltr)
		}
	case reflect.Array, reflect.Slice:
		buf.WriteByte('[')
		len := val.Len()
		for i := 0; i < len; i++ {
			if i != 0 {
				buf.WriteByte(',')
			}
			writeValue(buf, val.Index(i), fltr)
		}
		buf.WriteByte(']')
	case reflect.Map:
		mk := val.MapKeys()
		items := make([]item, len(mk), len(mk))
		// Get all values
		for i, _ := range items {
			items[i].name = formatValue(mk[i], fltr)
			items[i].value = val.MapIndex(mk[i])
		}

		// Sort values by key
		sort.Sort(itemSorter(items))

		buf.WriteByte('[')
		for i, _ := range items {
			if i != 0 {
				buf.WriteByte(',')
			}
			buf.WriteString(items[i].name)
			buf.WriteByte(':')
			writeValue(buf, items[i].value, fltr)
		}
		buf.WriteByte(']')
	case reflect.Struct:
		vtype := val.Type()
		flen := vtype.NumField()
		items := make([]item, 0, flen)
		// Get all fields
		for i := 0; i < flen; i++ {
			field := vtype.Field(i)
			it := item{field.Name, val.Field(i)}
			if fltr != nil {
				if ok := fltr(field, &it); !ok {
					continue
				}
			}
			items = append(items, it)
		}
		// Sort fields by name
		sort.Sort(itemSorter(items))

		buf.WriteByte('{')
		for i, _ := range items {
			if i != 0 {
				buf.WriteByte(',')
			}
			buf.WriteString(items[i].name)
			buf.WriteByte(':')
			writeValue(buf, items[i].value, fltr)
		}
		buf.WriteByte('}')
	case reflect.Interface:
		writeValue(buf, reflect.ValueOf(val.Interface()), fltr)
	default:
		buf.WriteString(val.String())
	}
}

func formatValue(val reflect.Value, fltr structFieldFilter) string {
	if val.Kind() == reflect.String {
		return "\"" + val.String() + "\""
	}

	var buf bytes.Buffer
	writeValue(&buf, val, fltr)

	return string(buf.Bytes())
}

func filterField(f reflect.StructField, i *item, version int) bool {
	var err error
	ver := 0
	lastver := -1
	if str := f.Tag.Get("hash"); str != "" {
		if str == "-" {
			return false
		}
		for _, tag := range strings.Split(str, " ") {
			args := strings.Split(strings.TrimSpace(tag), ":")
			if len(args) != 2 {
				return false
			}
			switch args[0] {
			case "name":
				i.name = args[1]
			case "version":
				if ver, err = strconv.Atoi(args[1]); err != nil {
					return false
				}
			case "lastversion":
				if lastver, err = strconv.Atoi(args[1]); err != nil {
					return false
				}
			}
		}
	} else {
		if str := f.Tag.Get("lastversion"); str != "" {
			if lastver, err = strconv.Atoi(str); err != nil {
				return false
			}
		}
		if str := f.Tag.Get("version"); str != "" {
			if ver, err = strconv.Atoi(str); err != nil {
				return false
			}
		}
	}
	if lastver != -1 && lastver < version {
		return false
	}
	if ver > version {
		return false
	}
	return true
}

func serialize(object interface{}, version int) []byte {
	var buf bytes.Buffer

	writeValue(&buf, reflect.ValueOf(object),
		func(f reflect.StructField, i *item) bool {
			return filterField(f, i, version)
		})

	return buf.Bytes()
}
<|endoftext|>


<commit_before>package isolated

import (
	. "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers"
	"code.cloudfoundry.org/cli/integration/helpers"

	"regexp"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

const (
	PushCommandName = "push"
)

var _ = Describe("start command", func() {
	var (
		orgName   string
		spaceName string
		appName   string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		appName = helpers.PrefixedRandomName("app")
	})

	Describe("help", func() {
		When("--help flag is set", func() {
			It("appears in cf help -a", func() {
				session := helpers.CF("help", "-a")
				Eventually(session).Should(Exit(0))
				Expect(session).To(HaveCommandInCategoryWithDescription("start", "APPS", "Start an app"))
			})

			It("Displays command usage to output", func() {
				session := helpers.CF("start", "--help")

				Eventually(session).Should(Say("NAME:"))
				Eventually(session).Should(Say("start - Start an app"))
				Eventually(session).Should(Say("USAGE:"))
				Eventually(session).Should(Say("cf start APP_NAME"))
				Eventually(session).Should(Say("ALIAS:"))
				Eventually(session).Should(Say("st"))
				Eventually(session).Should(Say("ENVIRONMENT:"))
				Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`))
				Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`))
				Eventually(session).Should(Say("SEE ALSO:"))
				Eventually(session).Should(Say("apps, logs, restart, run-task, scale, ssh, stop"))

				Eventually(session).Should(Exit(0))
			})
		})
	})

	When("the app name is not provided", func() {
		It("tells the user that the app name is required, prints help text, and exits 1", func() {
			session := helpers.CF("start")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `APP_NAME` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	When("the environment is not setup correctly", func() {
		It("fails with the appropriate errors", func() {
			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "start", appName)
		})
	})

	When("the environment is set up correctly", func() {
		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			Eventually(helpers.CF("create-app", appName)).Should(Exit(0))
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		When("the app exists", func() {
			When("the app does not need to be staged", func() {
				BeforeEach(func() {
					var packageGUID string

					// TODO: uncomment when map-route works in V7
					// mapRouteSession := helpers.CF("map-route", appName, helpers.DefaultSharedDomain(), "-n", appName)
					// Eventually(mapRouteSession).Should(Exit(0))

					helpers.WithHelloWorldApp(func(dir string) {
						pkgSession := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, "create-package", appName)
						Eventually(pkgSession).Should(Exit(0))
						regex := regexp.MustCompile(`Package with guid '(.+)' has been created.`)
						matches := regex.FindStringSubmatch(string(pkgSession.Out.Contents()))
						Expect(matches).To(HaveLen(2))

						packageGUID = matches[1]
					})

					stageSession := helpers.CF("stage", appName, "--package-guid", packageGUID)
					Eventually(stageSession).Should(Exit(0))

					regex := regexp.MustCompile(`droplet guid:\s+(.+)`)
					matches := regex.FindStringSubmatch(string(stageSession.Out.Contents()))
					Expect(matches).To(HaveLen(2))

					dropletGUID := matches[1]
					setDropletSession := helpers.CF("set-droplet", appName, "--droplet-guid", dropletGUID)
					Eventually(setDropletSession).Should(Exit(0))
				})

				It("starts the app", func() {
					userName, _ := helpers.GetCredentials()

					session := helpers.CF("start", appName)
					Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
					Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
					Eventually(session).Should(Say(`name:\s+%s`, appName))
					Eventually(session).Should(Say(`requested state:\s+started`))
					// TODO: uncomment when map-route works in V7
					// Eventually(session).Should(Say(`routes:\s+%s.%s`, appName, helpers.DefaultSharedDomain()))
					Eventually(session).Should(Say(`type:\s+web`))
					Eventually(session).Should(Say(`instances:\s+1/1`))
					Eventually(session).Should(Say(`memory usage:\s+32M`))
					Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`))
					Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`))

					Eventually(session).Should(Exit(0))
				})

				When("the app is already started", func() {
					BeforeEach(func() {
						Eventually(helpers.CF("start", appName)).Should(Exit(0))
					})

					It("displays app already started and exits 0", func() {
						session := helpers.CF("start", appName)

						Eventually(session).Should(Say(`App '%s' is already started\.`, appName))
						Eventually(session).Should(Say("OK"))

						Eventually(session).Should(Exit(0))
					})
				})
			})

			When("the app needs to be staged", func() {
				var packageGUID = ""
				BeforeEach(func() {
					helpers.WithHelloWorldApp(func(dir string) {
						session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, PushCommandName, appName)
						Eventually(session).Should(Say(`\s+name:\s+%s`, appName))
						Eventually(session).Should(Say(`requested state:\s+started`))
						Eventually(session).Should(Exit(0))
					})

					session := helpers.CF("stop", appName)
					Eventually(session).Should(Say("OK"))

					helpers.WithBananaPantsApp(func(dir string) {
						pkgSession := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, "create-package", appName)
						Eventually(pkgSession).Should(Exit(0))
						regex := regexp.MustCompile(`Package with guid '(.+)' has been created.`)
						matches := regex.FindStringSubmatch(string(pkgSession.Out.Contents()))
						Expect(matches).To(HaveLen(2))

						packageGUID = matches[1]
					})
				})

				It("stages and starts the app", func() {
					session := helpers.CF("start", appName)

					Eventually(session).Should(Say(`Staging app and tracing logs`))

					Eventually(session).Should(Say(`Downloading staticfile_buildpack\.\.\.`), "Error streaming logs")
					Eventually(session).Should(Say(`Uploading droplet\.\.\.`), "Error streaming logs")
					Eventually(session).Should(Say(`Uploaded droplet \([\d.]+M\)`), "Error streaming logs")
					Eventually(session).Should(Say(`Uploading complete`), "Error streaming logs")

					Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
					Eventually(session).Should(Say(`name:\s+%s`, appName))
					Eventually(session).Should(Say(`requested state:\s+started`))
					Eventually(session).Should(Say(`type:\s+web`))
					Eventually(session).Should(Say(`instances:\s+1/1`))
					Eventually(session).Should(Say(`memory usage:\s+32M`))
					Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`))
					Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`))

					Eventually(session).Should(Exit(0))
					Expect(session.Err).ToNot(Say(`timeout connecting to log server, no log will be shown`))

					Expect(helpers.GetPackageFirstDroplet(packageGUID)).To(Equal(helpers.GetAppDroplet(helpers.AppGUID(appName))))
				})
			})

			When("the app cannot be started or staged", func() {
				It("gives an error", func() {
					session := helpers.CF("start", appName)

					Eventually(session.Err).Should(Say(`App can not start with out a package to stage or a droplet to run.`))
					Eventually(session).Should(Say("FAILED"))

					Eventually(session).Should(Exit(1))
				})
			})
		})

		When("the app does not exist", func() {
			It("displays app not found and exits 1", func() {
				invalidAppName := "invalid-app-name"
				session := helpers.CF("start", invalidAppName)

				Eventually(session.Err).Should(Say(`App '%s' not found\.`, invalidAppName))
				Eventually(session).Should(Say("FAILED"))

				Eventually(session).Should(Exit(1))
			})
		})
	})
})
<commit_msg>Use helper method to assert on staging log lines<commit_after>package isolated

import (
	. "code.cloudfoundry.org/cli/cf/util/testhelpers/matchers"
	"code.cloudfoundry.org/cli/integration/helpers"

	"regexp"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

const (
	PushCommandName = "push"
)

var _ = Describe("start command", func() {
	var (
		orgName   string
		spaceName string
		appName   string
	)

	BeforeEach(func() {
		orgName = helpers.NewOrgName()
		spaceName = helpers.NewSpaceName()
		appName = helpers.PrefixedRandomName("app")
	})

	Describe("help", func() {
		When("--help flag is set", func() {
			It("appears in cf help -a", func() {
				session := helpers.CF("help", "-a")
				Eventually(session).Should(Exit(0))
				Expect(session).To(HaveCommandInCategoryWithDescription("start", "APPS", "Start an app"))
			})

			It("Displays command usage to output", func() {
				session := helpers.CF("start", "--help")

				Eventually(session).Should(Say("NAME:"))
				Eventually(session).Should(Say("start - Start an app"))
				Eventually(session).Should(Say("USAGE:"))
				Eventually(session).Should(Say("cf start APP_NAME"))
				Eventually(session).Should(Say("ALIAS:"))
				Eventually(session).Should(Say("st"))
				Eventually(session).Should(Say("ENVIRONMENT:"))
				Eventually(session).Should(Say(`CF_STAGING_TIMEOUT=15\s+Max wait time for staging, in minutes`))
				Eventually(session).Should(Say(`CF_STARTUP_TIMEOUT=5\s+Max wait time for app instance startup, in minutes`))
				Eventually(session).Should(Say("SEE ALSO:"))
				Eventually(session).Should(Say("apps, logs, restart, run-task, scale, ssh, stop"))

				Eventually(session).Should(Exit(0))
			})
		})
	})

	When("the app name is not provided", func() {
		It("tells the user that the app name is required, prints help text, and exits 1", func() {
			session := helpers.CF("start")

			Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `APP_NAME` was not provided"))
			Eventually(session).Should(Say("NAME:"))
			Eventually(session).Should(Exit(1))
		})
	})

	When("the environment is not setup correctly", func() {
		It("fails with the appropriate errors", func() {
			helpers.CheckEnvironmentTargetedCorrectly(true, true, ReadOnlyOrg, "start", appName)
		})
	})

	When("the environment is set up correctly", func() {
		BeforeEach(func() {
			helpers.SetupCF(orgName, spaceName)
			Eventually(helpers.CF("create-app", appName)).Should(Exit(0))
		})

		AfterEach(func() {
			helpers.QuickDeleteOrg(orgName)
		})

		When("the app exists", func() {
			When("the app does not need to be staged", func() {
				BeforeEach(func() {
					var packageGUID string

					// TODO: uncomment when map-route works in V7
					// mapRouteSession := helpers.CF("map-route", appName, helpers.DefaultSharedDomain(), "-n", appName)
					// Eventually(mapRouteSession).Should(Exit(0))

					helpers.WithHelloWorldApp(func(dir string) {
						pkgSession := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, "create-package", appName)
						Eventually(pkgSession).Should(Exit(0))
						regex := regexp.MustCompile(`Package with guid '(.+)' has been created.`)
						matches := regex.FindStringSubmatch(string(pkgSession.Out.Contents()))
						Expect(matches).To(HaveLen(2))

						packageGUID = matches[1]
					})

					stageSession := helpers.CF("stage", appName, "--package-guid", packageGUID)
					Eventually(stageSession).Should(Exit(0))

					regex := regexp.MustCompile(`droplet guid:\s+(.+)`)
					matches := regex.FindStringSubmatch(string(stageSession.Out.Contents()))
					Expect(matches).To(HaveLen(2))

					dropletGUID := matches[1]
					setDropletSession := helpers.CF("set-droplet", appName, "--droplet-guid", dropletGUID)
					Eventually(setDropletSession).Should(Exit(0))
				})

				It("starts the app", func() {
					userName, _ := helpers.GetCredentials()

					session := helpers.CF("start", appName)
					Eventually(session).Should(Say(`Starting app %s in org %s / space %s as %s\.\.\.`, appName, orgName, spaceName, userName))
					Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
					Eventually(session).Should(Say(`name:\s+%s`, appName))
					Eventually(session).Should(Say(`requested state:\s+started`))
					// TODO: uncomment when map-route works in V7
					// Eventually(session).Should(Say(`routes:\s+%s.%s`, appName, helpers.DefaultSharedDomain()))
					Eventually(session).Should(Say(`type:\s+web`))
					Eventually(session).Should(Say(`instances:\s+1/1`))
					Eventually(session).Should(Say(`memory usage:\s+32M`))
					Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`))
					Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`))

					Eventually(session).Should(Exit(0))
				})

				When("the app is already started", func() {
					BeforeEach(func() {
						Eventually(helpers.CF("start", appName)).Should(Exit(0))
					})

					It("displays app already started and exits 0", func() {
						session := helpers.CF("start", appName)

						Eventually(session).Should(Say(`App '%s' is already started\.`, appName))
						Eventually(session).Should(Say("OK"))

						Eventually(session).Should(Exit(0))
					})
				})
			})

			When("the app needs to be staged", func() {
				var packageGUID = ""
				BeforeEach(func() {
					helpers.WithHelloWorldApp(func(dir string) {
						session := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, PushCommandName, appName)
						Eventually(session).Should(Say(`\s+name:\s+%s`, appName))
						Eventually(session).Should(Say(`requested state:\s+started`))
						Eventually(session).Should(Exit(0))
					})

					session := helpers.CF("stop", appName)
					Eventually(session).Should(Say("OK"))

					helpers.WithBananaPantsApp(func(dir string) {
						pkgSession := helpers.CustomCF(helpers.CFEnv{WorkingDirectory: dir}, "create-package", appName)
						Eventually(pkgSession).Should(Exit(0))
						regex := regexp.MustCompile(`Package with guid '(.+)' has been created.`)
						matches := regex.FindStringSubmatch(string(pkgSession.Out.Contents()))
						Expect(matches).To(HaveLen(2))

						packageGUID = matches[1]
					})
				})

				It("stages and starts the app", func() {
					session := helpers.CF("start", appName)

					Eventually(session).Should(Say(`Staging app and tracing logs`))
					helpers.ConfirmStagingLogs(session)

					Eventually(session).Should(Say(`Waiting for app to start\.\.\.`))
					Eventually(session).Should(Say(`name:\s+%s`, appName))
					Eventually(session).Should(Say(`requested state:\s+started`))
					Eventually(session).Should(Say(`type:\s+web`))
					Eventually(session).Should(Say(`instances:\s+1/1`))
					Eventually(session).Should(Say(`memory usage:\s+32M`))
					Eventually(session).Should(Say(`\s+state\s+since\s+cpu\s+memory\s+disk\s+details`))
					Eventually(session).Should(Say(`#0\s+(starting|running)\s+\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`))

					Eventually(session).Should(Exit(0))
					Expect(session.Err).ToNot(Say(`timeout connecting to log server, no log will be shown`))

					Expect(helpers.GetPackageFirstDroplet(packageGUID)).To(Equal(helpers.GetAppDroplet(helpers.AppGUID(appName))))
				})
			})

			When("the app cannot be started or staged", func() {
				It("gives an error", func() {
					session := helpers.CF("start", appName)

					Eventually(session.Err).Should(Say(`App can not start with out a package to stage or a droplet to run.`))
					Eventually(session).Should(Say("FAILED"))

					Eventually(session).Should(Exit(1))
				})
			})
		})

		When("the app does not exist", func() {
			It("displays app not found and exits 1", func() {
				invalidAppName := "invalid-app-name"
				session := helpers.CF("start", invalidAppName)

				Eventually(session.Err).Should(Say(`App '%s' not found\.`, invalidAppName))
				Eventually(session).Should(Say("FAILED"))

				Eventually(session).Should(Exit(1))
			})
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package ra

import (
	"crypto/x509"
	"fmt"
	"math/big"
	"net/url"
	"regexp"
	"strconv"
	"time"

	"github.com/letsencrypt/boulder/core"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/policy"
)

// All of the fields in RegistrationAuthorityImpl need to be
// populated, or there is a risk of panic.
type RegistrationAuthorityImpl struct {
	CA  core.CertificateAuthority
	VA  core.ValidationAuthority
	SA  core.StorageAuthority
	PA  core.PolicyAuthority
	log *blog.AuditLogger

	AuthzBase string
}

func NewRegistrationAuthorityImpl() RegistrationAuthorityImpl {
	logger := blog.GetAuditLogger()
	logger.Notice("Registration Authority Starting")

	ra := RegistrationAuthorityImpl{log: logger}
	ra.PA = policy.NewPolicyAuthorityImpl()
	return ra
}

var allButLastPathSegment = regexp.MustCompile("^.*/")

func lastPathSegment(url core.AcmeURL) string {
	return allButLastPathSegment.ReplaceAllString(url.Path, "")
}

type certificateRequestEvent struct {
	ID                  string    `json:",omitempty"`
	Requester           int64     `json:",omitempty"`
	SerialNumber        *big.Int  `json:",omitempty"`
	RequestMethod       string    `json:",omitempty"`
	VerificationMethods []string  `json:",omitempty"`
	VerifiedFields      []string  `json:",omitempty"`
	CommonName          string    `json:",omitempty"`
	Names               []string  `json:",omitempty"`
	NotBefore           time.Time `json:",omitempty"`
	NotAfter            time.Time `json:",omitempty"`
	RequestTime         time.Time `json:",omitempty"`
	ResponseTime        time.Time `json:",omitempty"`
	Error               string    `json:",omitempty"`
}

func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (reg core.Registration, err error) {
	if !core.GoodKey(init.Key.Key) {
		return core.Registration{}, fmt.Errorf("Invalid public key.")
	}
	reg = core.Registration{
		RecoveryToken: core.NewToken(),
		Key:           init.Key,
	}
	reg.MergeUpdate(init)

	// Store the authorization object, then return it
	reg, err = ra.SA.NewRegistration(reg)
	return
}

func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization, regID int64) (authz core.Authorization, err error) {
	if regID <= 0 {
		err = fmt.Errorf("Invalid registration ID")
		return authz, err
	}

	identifier := request.Identifier

	// Check that the identifier is present and appropriate
	if err = ra.PA.WillingToIssue(identifier); err != nil {
		return authz, err
	}

	// Create validations, but we have to update them with URIs later
	challenges, combinations := ra.PA.ChallengesFor(identifier)

	// Partially-filled object
	authz = core.Authorization{
		Identifier:     identifier,
		RegistrationID: regID,
		Status:         core.StatusPending,
		Combinations:   combinations,
	}

	// Get a pending Auth first so we can get our ID back, then update with challenges
	authz, err = ra.SA.NewPendingAuthorization(authz)
	if err != nil {
		return authz, err
	}

	// Construct all the challenge URIs
	for i := range challenges {
		// Ignoring these errors because we construct the URLs to be correct
		challengeURI, _ := url.Parse(ra.AuthzBase + authz.ID + "?challenge=" + strconv.Itoa(i))
		challenges[i].URI = core.AcmeURL(*challengeURI)

		if !challenges[i].IsSane(false) {
			err = fmt.Errorf("Challenge didn't pass sanity check: %+v", challenges[i])
			return authz, err
		}
	}

	// Update object
	authz.Challenges = challenges

	// Store the authorization object, then return it
	err = ra.SA.UpdatePendingAuthorization(authz)
	return authz, err
}

func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (core.Certificate, error) {
	emptyCert := core.Certificate{}
	var err error
	var logEventResult string

	// Assume the worst
	logEventResult = "error"

	// Construct the log event
	logEvent := certificateRequestEvent{
		ID:            core.NewToken(),
		Requester:     regID,
		RequestMethod: "online",
		RequestTime:   time.Now(),
	}

	// No matter what, log the request
	defer func() {
		// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
		ra.log.AuditObject(fmt.Sprintf("Certificate request - %s", logEventResult), logEvent)
	}()

	if regID <= 0 {
		err = fmt.Errorf("Invalid registration ID")
		return emptyCert, err
	}

	// Verify the CSR
	// TODO: Verify that other aspects of the CSR are appropriate
	csr := req.CSR
	if err = core.VerifyCSR(csr); err != nil {
		logEvent.Error = err.Error()
		err = core.UnauthorizedError("Invalid signature on CSR")
		return emptyCert, err
	}

	logEvent.CommonName = csr.Subject.CommonName
	logEvent.Names = csr.DNSNames

	// Validate that authorization key is authorized for all domains
	names := make([]string, len(csr.DNSNames))
	copy(names, csr.DNSNames)
	if len(csr.Subject.CommonName) > 0 {
		names = append(names, csr.Subject.CommonName)
	}

	csrPreviousDenied, err := ra.SA.AlreadyDeniedCSR(names)
	if err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}
	if csrPreviousDenied {
		err = core.UnauthorizedError("CSR has already been revoked/denied")
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	registration, err := ra.SA.GetRegistration(regID)
	if err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	if core.KeyDigestEquals(csr.PublicKey, registration.Key) {
		err = core.MalformedRequestError("Certificate public key must be different than account key")
		return emptyCert, err
	}

	// Gather authorized domains from the referenced authorizations
	authorizedDomains := map[string]bool{}
	verificationMethodSet := map[string]bool{}
	now := time.Now()
	for _, url := range req.Authorizations {
		id := lastPathSegment(url)
		authz, err := ra.SA.GetAuthorization(id)
		if err != nil || // Couldn't find authorization
			authz.RegistrationID != registration.ID ||
			authz.Status != core.StatusValid || // Not finalized or not successful
			authz.Expires.Before(now) || // Expired
			authz.Identifier.Type != core.IdentifierDNS {
			// XXX: It may be good to fail here instead of ignoring invalid authorizations.
			//      However, it seems like this treatment is more in the spirit of Postel's
			//      law, and it hides information from attackers.
			continue
		}

		for _, challenge := range authz.Challenges {
			if challenge.Status == core.StatusValid {
				verificationMethodSet[challenge.Type] = true
			}
		}

		authorizedDomains[authz.Identifier.Value] = true
	}
	verificationMethods := []string{}
	for method, _ := range verificationMethodSet {
		verificationMethods = append(verificationMethods, method)
	}
	logEvent.VerificationMethods = verificationMethods

	// Validate all domains
	for _, name := range names {
		if !authorizedDomains[name] {
			err = core.UnauthorizedError(fmt.Sprintf("Key not authorized for name %s", name))
			logEvent.Error = err.Error()
			return emptyCert, err
		}
	}

	// Mark that we verified the CN and SANs
	logEvent.VerifiedFields = []string{"subject.commonName", "subjectAltName"}

	// Create the certificate and log the result
	var cert core.Certificate
	if cert, err = ra.CA.IssueCertificate(*csr, regID); err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
	if err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	logEvent.SerialNumber = parsedCertificate.SerialNumber
	logEvent.CommonName = parsedCertificate.Subject.CommonName
	logEvent.NotBefore = parsedCertificate.NotBefore
	logEvent.NotAfter = parsedCertificate.NotAfter
	logEvent.ResponseTime = time.Now()

	logEventResult = "successful"
	return cert, nil
}

func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
	base.MergeUpdate(update)
	reg = base
	err = ra.SA.UpdateRegistration(base)
	return
}

func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization, challengeIndex int, response core.Challenge) (authz core.Authorization, err error) {
	// Copy information over that the client is allowed to supply
	authz = base
	if challengeIndex >= len(authz.Challenges) {
		err = core.MalformedRequestError("Invalid challenge index")
		return
	}
	authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response)

	// Store the updated version
	if err = ra.SA.UpdatePendingAuthorization(authz); err != nil {
		return
	}

	// Dispatch to the VA for service
	ra.VA.UpdateValidations(authz, challengeIndex)

	return
}

func (ra *RegistrationAuthorityImpl) RevokeCertificate(cert x509.Certificate) error {
	serialString := core.SerialToString(cert.SerialNumber)
	err := ra.CA.RevokeCertificate(serialString, 0)

	// AUDIT[ Revocation Requests ] 4e85d791-09c0-4ab3-a837-d3d67e945134
	if err != nil {
		ra.log.Audit(fmt.Sprintf("Revocation error - %s - %s", serialString, err))
	} else {
		ra.log.Audit(fmt.Sprintf("Revocation - %s", serialString))
	}

	return err
}

func (ra *RegistrationAuthorityImpl) OnValidationUpdate(authz core.Authorization) error {
	// Check to see whether the updated validations are sufficient
	// Current policy is to accept if any validation succeeded
	for _, val := range authz.Challenges {
		if val.Status == core.StatusValid {
			authz.Status = core.StatusValid
			break
		}
	}

	// If no validation succeeded, then the authorization is invalid
	// NOTE: This only works because we only ever do one validation
	if authz.Status != core.StatusValid {
		authz.Status = core.StatusInvalid
	} else {
		// TODO: Enable configuration of expiry time
		authz.Expires = time.Now().Add(365 * 24 * time.Hour)
	}

	// Finalize the authorization (error ignored)
	return ra.SA.FinalizeAuthorization(authz)
}
<commit_msg>Issue #211 (partial)<commit_after>// Copyright 2014 ISRG.  All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package ra

import (
	"crypto/x509"
	"fmt"
	"math/big"
	"net/url"
	"regexp"
	"strconv"
	"time"

	"github.com/letsencrypt/boulder/core"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/policy"
)

// All of the fields in RegistrationAuthorityImpl need to be
// populated, or there is a risk of panic.
type RegistrationAuthorityImpl struct {
	CA  core.CertificateAuthority
	VA  core.ValidationAuthority
	SA  core.StorageAuthority
	PA  core.PolicyAuthority
	log *blog.AuditLogger

	AuthzBase string
}

func NewRegistrationAuthorityImpl() RegistrationAuthorityImpl {
	logger := blog.GetAuditLogger()
	logger.Notice("Registration Authority Starting")

	ra := RegistrationAuthorityImpl{log: logger}
	ra.PA = policy.NewPolicyAuthorityImpl()
	return ra
}

var allButLastPathSegment = regexp.MustCompile("^.*/")

func lastPathSegment(url core.AcmeURL) string {
	return allButLastPathSegment.ReplaceAllString(url.Path, "")
}

type certificateRequestEvent struct {
	ID                  string    `json:",omitempty"`
	Requester           int64     `json:",omitempty"`
	SerialNumber        *big.Int  `json:",omitempty"`
	RequestMethod       string    `json:",omitempty"`
	VerificationMethods []string  `json:",omitempty"`
	VerifiedFields      []string  `json:",omitempty"`
	CommonName          string    `json:",omitempty"`
	Names               []string  `json:",omitempty"`
	NotBefore           time.Time `json:",omitempty"`
	NotAfter            time.Time `json:",omitempty"`
	RequestTime         time.Time `json:",omitempty"`
	ResponseTime        time.Time `json:",omitempty"`
	Error               string    `json:",omitempty"`
}

func (ra *RegistrationAuthorityImpl) NewRegistration(init core.Registration) (reg core.Registration, err error) {
	if !core.GoodKey(init.Key.Key) {
		return core.Registration{}, fmt.Errorf("Invalid public key.")
	}
	reg = core.Registration{
		RecoveryToken: core.NewToken(),
		Key:           init.Key,
	}
	reg.MergeUpdate(init)

	// Store the authorization object, then return it
	reg, err = ra.SA.NewRegistration(reg)
	return
}

func (ra *RegistrationAuthorityImpl) NewAuthorization(request core.Authorization, regID int64) (authz core.Authorization, err error) {
	if regID <= 0 {
		err = fmt.Errorf("Invalid registration ID")
		return authz, err
	}

	identifier := request.Identifier

	// Check that the identifier is present and appropriate
	if err = ra.PA.WillingToIssue(identifier); err != nil {
		return authz, err
	}

	// Create validations, but we have to update them with URIs later
	challenges, combinations := ra.PA.ChallengesFor(identifier)

	// Partially-filled object
	authz = core.Authorization{
		Identifier:     identifier,
		RegistrationID: regID,
		Status:         core.StatusPending,
		Combinations:   combinations,
	}

	// Get a pending Auth first so we can get our ID back, then update with challenges
	authz, err = ra.SA.NewPendingAuthorization(authz)
	if err != nil {
		return authz, err
	}

	// Construct all the challenge URIs
	for i := range challenges {
		// Ignoring these errors because we construct the URLs to be correct
		challengeURI, _ := url.Parse(ra.AuthzBase + authz.ID + "?challenge=" + strconv.Itoa(i))
		challenges[i].URI = core.AcmeURL(*challengeURI)

		if !challenges[i].IsSane(false) {
			err = fmt.Errorf("Challenge didn't pass sanity check: %+v", challenges[i])
			return authz, err
		}
	}

	// Update object
	authz.Challenges = challenges

	// Store the authorization object, then return it
	err = ra.SA.UpdatePendingAuthorization(authz)
	return authz, err
}

func (ra *RegistrationAuthorityImpl) NewCertificate(req core.CertificateRequest, regID int64) (core.Certificate, error) {
	emptyCert := core.Certificate{}
	var err error
	var logEventResult string

	// Assume the worst
	logEventResult = "error"

	// Construct the log event
	logEvent := certificateRequestEvent{
		ID:            core.NewToken(),
		Requester:     regID,
		RequestMethod: "online",
		RequestTime:   time.Now(),
	}

	// No matter what, log the request
	defer func() {
		// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
		ra.log.AuditObject(fmt.Sprintf("Certificate request - %s", logEventResult), logEvent)
	}()

	if regID <= 0 {
		err = fmt.Errorf("Invalid registration ID")
		return emptyCert, err
	}

	// Verify the CSR
	// TODO: Verify that other aspects of the CSR are appropriate
	csr := req.CSR
	if err = core.VerifyCSR(csr); err != nil {
		logEvent.Error = err.Error()
		err = core.UnauthorizedError("Invalid signature on CSR")
		return emptyCert, err
	}

	logEvent.CommonName = csr.Subject.CommonName
	logEvent.Names = csr.DNSNames

	// Validate that authorization key is authorized for all domains
	names := make([]string, len(csr.DNSNames))
	copy(names, csr.DNSNames)
	if len(csr.Subject.CommonName) > 0 {
		names = append(names, csr.Subject.CommonName)
	}

	if len(names) == 0 {
		err = core.UnauthorizedError("CSR has no names in it")
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	csrPreviousDenied, err := ra.SA.AlreadyDeniedCSR(names)
	if err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}
	if csrPreviousDenied {
		err = core.UnauthorizedError("CSR has already been revoked/denied")
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	registration, err := ra.SA.GetRegistration(regID)
	if err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	if core.KeyDigestEquals(csr.PublicKey, registration.Key) {
		err = core.MalformedRequestError("Certificate public key must be different than account key")
		return emptyCert, err
	}

	// Gather authorized domains from the referenced authorizations
	authorizedDomains := map[string]bool{}
	verificationMethodSet := map[string]bool{}
	now := time.Now()
	for _, url := range req.Authorizations {
		id := lastPathSegment(url)
		authz, err := ra.SA.GetAuthorization(id)
		if err != nil || // Couldn't find authorization
			authz.RegistrationID != registration.ID ||
			authz.Status != core.StatusValid || // Not finalized or not successful
			authz.Expires.Before(now) || // Expired
			authz.Identifier.Type != core.IdentifierDNS {
			// XXX: It may be good to fail here instead of ignoring invalid authorizations.
			//      However, it seems like this treatment is more in the spirit of Postel's
			//      law, and it hides information from attackers.
			continue
		}

		for _, challenge := range authz.Challenges {
			if challenge.Status == core.StatusValid {
				verificationMethodSet[challenge.Type] = true
			}
		}

		authorizedDomains[authz.Identifier.Value] = true
	}
	verificationMethods := []string{}
	for method, _ := range verificationMethodSet {
		verificationMethods = append(verificationMethods, method)
	}
	logEvent.VerificationMethods = verificationMethods

	// Validate all domains
	for _, name := range names {
		if !authorizedDomains[name] {
			err = core.UnauthorizedError(fmt.Sprintf("Key not authorized for name %s", name))
			logEvent.Error = err.Error()
			return emptyCert, err
		}
	}

	// Mark that we verified the CN and SANs
	logEvent.VerifiedFields = []string{"subject.commonName", "subjectAltName"}

	// Create the certificate and log the result
	var cert core.Certificate
	if cert, err = ra.CA.IssueCertificate(*csr, regID); err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
	if err != nil {
		logEvent.Error = err.Error()
		return emptyCert, err
	}

	logEvent.SerialNumber = parsedCertificate.SerialNumber
	logEvent.CommonName = parsedCertificate.Subject.CommonName
	logEvent.NotBefore = parsedCertificate.NotBefore
	logEvent.NotAfter = parsedCertificate.NotAfter
	logEvent.ResponseTime = time.Now()

	logEventResult = "successful"
	return cert, nil
}

func (ra *RegistrationAuthorityImpl) UpdateRegistration(base core.Registration, update core.Registration) (reg core.Registration, err error) {
	base.MergeUpdate(update)
	reg = base
	err = ra.SA.UpdateRegistration(base)
	return
}

func (ra *RegistrationAuthorityImpl) UpdateAuthorization(base core.Authorization, challengeIndex int, response core.Challenge) (authz core.Authorization, err error) {
	// Copy information over that the client is allowed to supply
	authz = base
	if challengeIndex >= len(authz.Challenges) {
		err = core.MalformedRequestError("Invalid challenge index")
		return
	}
	authz.Challenges[challengeIndex] = authz.Challenges[challengeIndex].MergeResponse(response)

	// Store the updated version
	if err = ra.SA.UpdatePendingAuthorization(authz); err != nil {
		return
	}

	// Dispatch to the VA for service
	ra.VA.UpdateValidations(authz, challengeIndex)

	return
}

func (ra *RegistrationAuthorityImpl) RevokeCertificate(cert x509.Certificate) error {
	serialString := core.SerialToString(cert.SerialNumber)
	err := ra.CA.RevokeCertificate(serialString, 0)

	// AUDIT[ Revocation Requests ] 4e85d791-09c0-4ab3-a837-d3d67e945134
	if err != nil {
		ra.log.Audit(fmt.Sprintf("Revocation error - %s - %s", serialString, err))
	} else {
		ra.log.Audit(fmt.Sprintf("Revocation - %s", serialString))
	}

	return err
}

func (ra *RegistrationAuthorityImpl) OnValidationUpdate(authz core.Authorization) error {
	// Check to see whether the updated validations are sufficient
	// Current policy is to accept if any validation succeeded
	for _, val := range authz.Challenges {
		if val.Status == core.StatusValid {
			authz.Status = core.StatusValid
			break
		}
	}

	// If no validation succeeded, then the authorization is invalid
	// NOTE: This only works because we only ever do one validation
	if authz.Status != core.StatusValid {
		authz.Status = core.StatusInvalid
	} else {
		// TODO: Enable configuration of expiry time
		authz.Expires = time.Now().Add(365 * 24 * time.Hour)
	}

	// Finalize the authorization (error ignored)
	return ra.SA.FinalizeAuthorization(authz)
}
<|endoftext|>


<commit_before>package corehandlers_test

import (
	"fmt"
	"testing"

	"github.com/golib/assert"
	"github.com/golib/aws/internal"
	"github.com/golib/aws/internal/awserr"
	"github.com/golib/aws/internal/client"
	"github.com/golib/aws/internal/client/metadata"
	"github.com/golib/aws/internal/corehandlers"
	"github.com/golib/aws/internal/request"
	"github.com/stretchr/testify/require"
)

var testSvc = func() *client.Client {
	s := &client.Client{
		Config: internal.Config{},
		ClientInfo: metadata.ClientInfo{
			ServiceName: "mock-service",
			APIVersion:  "2015-01-01",
		},
	}
	return s
}()

type StructShape struct {
	_ struct{} `type:"structure"`

	RequiredList   []*ConditionalStructShape          `required:"true"`
	RequiredMap    map[string]*ConditionalStructShape `required:"true"`
	RequiredBool   *bool                              `required:"true"`
	OptionalStruct *ConditionalStructShape

	hiddenParameter *string
}

func (s *StructShape) Validate() error {
	invalidParams := request.ErrInvalidParams{Context: "StructShape"}
	if s.RequiredList == nil {
		invalidParams.Add(request.NewErrParamRequired("RequiredList"))
	}
	if s.RequiredMap == nil {
		invalidParams.Add(request.NewErrParamRequired("RequiredMap"))
	}
	if s.RequiredBool == nil {
		invalidParams.Add(request.NewErrParamRequired("RequiredBool"))
	}
	if s.RequiredList != nil {
		for i, v := range s.RequiredList {
			if v == nil {
				continue
			}
			if err := v.Validate(); err != nil {
				invalidParams.AddNested(fmt.Sprintf("%s[%v]", "RequiredList", i), err.(request.ErrInvalidParams))
			}
		}
	}
	if s.RequiredMap != nil {
		for i, v := range s.RequiredMap {
			if v == nil {
				continue
			}
			if err := v.Validate(); err != nil {
				invalidParams.AddNested(fmt.Sprintf("%s[%v]", "RequiredMap", i), err.(request.ErrInvalidParams))
			}
		}
	}
	if s.OptionalStruct != nil {
		if err := s.OptionalStruct.Validate(); err != nil {
			invalidParams.AddNested("OptionalStruct", err.(request.ErrInvalidParams))
		}
	}

	if invalidParams.Len() > 0 {
		return invalidParams
	}
	return nil
}

type ConditionalStructShape struct {
	_ struct{} `type:"structure"`

	Name *string `required:"true"`
}

func (s *ConditionalStructShape) Validate() error {
	invalidParams := request.ErrInvalidParams{Context: "ConditionalStructShape"}
	if s.Name == nil {
		invalidParams.Add(request.NewErrParamRequired("Name"))
	}

	if invalidParams.Len() > 0 {
		return invalidParams
	}
	return nil
}

func TestNoErrors(t *testing.T) {
	input := &StructShape{
		RequiredList: []*ConditionalStructShape{},
		RequiredMap: map[string]*ConditionalStructShape{
			"key1": {Name: internal.String("Name")},
			"key2": {Name: internal.String("Name")},
		},
		RequiredBool:   internal.Bool(true),
		OptionalStruct: &ConditionalStructShape{Name: internal.String("Name")},
	}

	req := testSvc.NewRequest(&request.Operation{}, input, nil)
	corehandlers.ValidateParametersHandler.Fn(req)
	require.NoError(t, req.Error)
}

func TestMissingRequiredParameters(t *testing.T) {
	input := &StructShape{}
	req := testSvc.NewRequest(&request.Operation{}, input, nil)
	corehandlers.ValidateParametersHandler.Fn(req)

	require.Error(t, req.Error)
	assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
	assert.Equal(t, "3 validation error(s) found.", req.Error.(awserr.Error).Message())

	errs := req.Error.(awserr.BatchedErrors).OrigErrs()
	assert.Len(t, errs, 3)
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredList.", errs[0].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredMap.", errs[1].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredBool.", errs[2].Error())

	assert.Equal(t, "InvalidParameter: 3 validation error(s) found.\n- missing required field, StructShape.RequiredList.\n- missing required field, StructShape.RequiredMap.\n- missing required field, StructShape.RequiredBool.\n", req.Error.Error())
}

func TestNestedMissingRequiredParameters(t *testing.T) {
	input := &StructShape{
		RequiredList: []*ConditionalStructShape{{}},
		RequiredMap: map[string]*ConditionalStructShape{
			"key1": {Name: internal.String("Name")},
			"key2": {},
		},
		RequiredBool:   internal.Bool(true),
		OptionalStruct: &ConditionalStructShape{},
	}

	req := testSvc.NewRequest(&request.Operation{}, input, nil)
	corehandlers.ValidateParametersHandler.Fn(req)

	require.Error(t, req.Error)
	assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
	assert.Equal(t, "3 validation error(s) found.", req.Error.(awserr.Error).Message())

	errs := req.Error.(awserr.BatchedErrors).OrigErrs()
	assert.Len(t, errs, 3)
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredList[0].Name.", errs[0].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredMap[key2].Name.", errs[1].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.OptionalStruct.Name.", errs[2].Error())
}

type testInput struct {
	StringField *string           `min:"5"`
	ListField   []string          `min:"3"`
	MapField    map[string]string `min:"4"`
}

func (s testInput) Validate() error {
	invalidParams := request.ErrInvalidParams{Context: "testInput"}
	if s.StringField != nil && len(*s.StringField) < 5 {
		invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
	}
	if s.ListField != nil && len(s.ListField) < 3 {
		invalidParams.Add(request.NewErrParamMinLen("ListField", 3))
	}
	if s.MapField != nil && len(s.MapField) < 4 {
		invalidParams.Add(request.NewErrParamMinLen("MapField", 4))
	}

	if invalidParams.Len() > 0 {
		return invalidParams
	}
	return nil
}

var testsFieldMin = []struct {
	err awserr.Error
	in  testInput
}{
	{
		err: func() awserr.Error {
			invalidParams := request.ErrInvalidParams{Context: "testInput"}
			invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
			return invalidParams
		}(),
		in: testInput{StringField: internal.String("abcd")},
	},
	{
		err: func() awserr.Error {
			invalidParams := request.ErrInvalidParams{Context: "testInput"}
			invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
			invalidParams.Add(request.NewErrParamMinLen("ListField", 3))
			return invalidParams
		}(),
		in: testInput{StringField: internal.String("abcd"), ListField: []string{"a", "b"}},
	},
	{
		err: func() awserr.Error {
			invalidParams := request.ErrInvalidParams{Context: "testInput"}
			invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
			invalidParams.Add(request.NewErrParamMinLen("ListField", 3))
			invalidParams.Add(request.NewErrParamMinLen("MapField", 4))
			return invalidParams
		}(),
		in: testInput{StringField: internal.String("abcd"), ListField: []string{"a", "b"}, MapField: map[string]string{"a": "a", "b": "b"}},
	},
	{
		err: nil,
		in: testInput{StringField: internal.String("abcde"),
			ListField: []string{"a", "b", "c"}, MapField: map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}},
	},
}

func TestValidateFieldMinParameter(t *testing.T) {
	for i, c := range testsFieldMin {
		req := testSvc.NewRequest(&request.Operation{}, &c.in, nil)
		corehandlers.ValidateParametersHandler.Fn(req)

		assert.Equal(t, c.err, req.Error, "%d case failed", i)
	}
}
<commit_msg>replace require with assert<commit_after>package corehandlers_test

import (
	"fmt"
	"testing"

	"github.com/golib/assert"
	"github.com/golib/aws/internal"
	"github.com/golib/aws/internal/awserr"
	"github.com/golib/aws/internal/client"
	"github.com/golib/aws/internal/client/metadata"
	"github.com/golib/aws/internal/corehandlers"
	"github.com/golib/aws/internal/request"
)

var testSvc = func() *client.Client {
	s := &client.Client{
		Config: internal.Config{},
		ClientInfo: metadata.ClientInfo{
			ServiceName: "mock-service",
			APIVersion:  "2015-01-01",
		},
	}
	return s
}()

type StructShape struct {
	_ struct{} `type:"structure"`

	RequiredList   []*ConditionalStructShape          `required:"true"`
	RequiredMap    map[string]*ConditionalStructShape `required:"true"`
	RequiredBool   *bool                              `required:"true"`
	OptionalStruct *ConditionalStructShape

	hiddenParameter *string
}

func (s *StructShape) Validate() error {
	invalidParams := request.ErrInvalidParams{Context: "StructShape"}
	if s.RequiredList == nil {
		invalidParams.Add(request.NewErrParamRequired("RequiredList"))
	}
	if s.RequiredMap == nil {
		invalidParams.Add(request.NewErrParamRequired("RequiredMap"))
	}
	if s.RequiredBool == nil {
		invalidParams.Add(request.NewErrParamRequired("RequiredBool"))
	}
	if s.RequiredList != nil {
		for i, v := range s.RequiredList {
			if v == nil {
				continue
			}
			if err := v.Validate(); err != nil {
				invalidParams.AddNested(fmt.Sprintf("%s[%v]", "RequiredList", i), err.(request.ErrInvalidParams))
			}
		}
	}
	if s.RequiredMap != nil {
		for i, v := range s.RequiredMap {
			if v == nil {
				continue
			}
			if err := v.Validate(); err != nil {
				invalidParams.AddNested(fmt.Sprintf("%s[%v]", "RequiredMap", i), err.(request.ErrInvalidParams))
			}
		}
	}
	if s.OptionalStruct != nil {
		if err := s.OptionalStruct.Validate(); err != nil {
			invalidParams.AddNested("OptionalStruct", err.(request.ErrInvalidParams))
		}
	}

	if invalidParams.Len() > 0 {
		return invalidParams
	}
	return nil
}

type ConditionalStructShape struct {
	_ struct{} `type:"structure"`

	Name *string `required:"true"`
}

func (s *ConditionalStructShape) Validate() error {
	invalidParams := request.ErrInvalidParams{Context: "ConditionalStructShape"}
	if s.Name == nil {
		invalidParams.Add(request.NewErrParamRequired("Name"))
	}

	if invalidParams.Len() > 0 {
		return invalidParams
	}
	return nil
}

func TestNoErrors(t *testing.T) {
	input := &StructShape{
		RequiredList: []*ConditionalStructShape{},
		RequiredMap: map[string]*ConditionalStructShape{
			"key1": {Name: internal.String("Name")},
			"key2": {Name: internal.String("Name")},
		},
		RequiredBool:   internal.Bool(true),
		OptionalStruct: &ConditionalStructShape{Name: internal.String("Name")},
	}

	req := testSvc.NewRequest(&request.Operation{}, input, nil)
	corehandlers.ValidateParametersHandler.Fn(req)
	assert.NoError(t, req.Error)
}

func TestMissingRequiredParameters(t *testing.T) {
	input := &StructShape{}
	req := testSvc.NewRequest(&request.Operation{}, input, nil)
	corehandlers.ValidateParametersHandler.Fn(req)

	assert.Error(t, req.Error)
	assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
	assert.Equal(t, "3 validation error(s) found.", req.Error.(awserr.Error).Message())

	errs := req.Error.(awserr.BatchedErrors).OrigErrs()
	assert.Len(t, errs, 3)
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredList.", errs[0].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredMap.", errs[1].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredBool.", errs[2].Error())

	assert.Equal(t, "InvalidParameter: 3 validation error(s) found.\n- missing required field, StructShape.RequiredList.\n- missing required field, StructShape.RequiredMap.\n- missing required field, StructShape.RequiredBool.\n", req.Error.Error())
}

func TestNestedMissingRequiredParameters(t *testing.T) {
	input := &StructShape{
		RequiredList: []*ConditionalStructShape{{}},
		RequiredMap: map[string]*ConditionalStructShape{
			"key1": {Name: internal.String("Name")},
			"key2": {},
		},
		RequiredBool:   internal.Bool(true),
		OptionalStruct: &ConditionalStructShape{},
	}

	req := testSvc.NewRequest(&request.Operation{}, input, nil)
	corehandlers.ValidateParametersHandler.Fn(req)

	assert.Error(t, req.Error)
	assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
	assert.Equal(t, "3 validation error(s) found.", req.Error.(awserr.Error).Message())

	errs := req.Error.(awserr.BatchedErrors).OrigErrs()
	assert.Len(t, errs, 3)
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredList[0].Name.", errs[0].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.RequiredMap[key2].Name.", errs[1].Error())
	assert.Equal(t, "ParamRequiredError: missing required field, StructShape.OptionalStruct.Name.", errs[2].Error())
}

type testInput struct {
	StringField *string           `min:"5"`
	ListField   []string          `min:"3"`
	MapField    map[string]string `min:"4"`
}

func (s testInput) Validate() error {
	invalidParams := request.ErrInvalidParams{Context: "testInput"}
	if s.StringField != nil && len(*s.StringField) < 5 {
		invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
	}
	if s.ListField != nil && len(s.ListField) < 3 {
		invalidParams.Add(request.NewErrParamMinLen("ListField", 3))
	}
	if s.MapField != nil && len(s.MapField) < 4 {
		invalidParams.Add(request.NewErrParamMinLen("MapField", 4))
	}

	if invalidParams.Len() > 0 {
		return invalidParams
	}
	return nil
}

var testsFieldMin = []struct {
	err awserr.Error
	in  testInput
}{
	{
		err: func() awserr.Error {
			invalidParams := request.ErrInvalidParams{Context: "testInput"}
			invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
			return invalidParams
		}(),
		in: testInput{StringField: internal.String("abcd")},
	},
	{
		err: func() awserr.Error {
			invalidParams := request.ErrInvalidParams{Context: "testInput"}
			invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
			invalidParams.Add(request.NewErrParamMinLen("ListField", 3))
			return invalidParams
		}(),
		in: testInput{StringField: internal.String("abcd"), ListField: []string{"a", "b"}},
	},
	{
		err: func() awserr.Error {
			invalidParams := request.ErrInvalidParams{Context: "testInput"}
			invalidParams.Add(request.NewErrParamMinLen("StringField", 5))
			invalidParams.Add(request.NewErrParamMinLen("ListField", 3))
			invalidParams.Add(request.NewErrParamMinLen("MapField", 4))
			return invalidParams
		}(),
		in: testInput{StringField: internal.String("abcd"), ListField: []string{"a", "b"}, MapField: map[string]string{"a": "a", "b": "b"}},
	},
	{
		err: nil,
		in: testInput{StringField: internal.String("abcde"),
			ListField: []string{"a", "b", "c"}, MapField: map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}},
	},
}

func TestValidateFieldMinParameter(t *testing.T) {
	for i, c := range testsFieldMin {
		req := testSvc.NewRequest(&request.Operation{}, &c.in, nil)
		corehandlers.ValidateParametersHandler.Fn(req)

		assert.Equal(t, c.err, req.Error, "%d case failed", i)
	}
}
<|endoftext|>