<commit_before>package main

import (
	"io/ioutil"
	"log"
	"math/rand"

	pb "github.com/brotherlogic/gobuildmaster/proto"
	pbs "github.com/brotherlogic/gobuildslave/proto"
	"github.com/golang/protobuf/proto"

	pbd "github.com/brotherlogic/discovery/proto"
)

type checker interface {
	assess(server string) (*pbs.JobList, *pbs.Config)
	discover() *pbd.ServiceList
}

func getFleetStatus(c checker) (map[string]*pbs.JobList, map[string]*pbs.Config) {
	resJ := make(map[string]*pbs.JobList)
	resC := make(map[string]*pbs.Config)

	for _, service := range c.discover().Services {
		log.Printf("Found: %v", service)
		if service.Name == "gobuildslave" {
			log.Printf("HERE: %v", service)
			joblist, config := c.assess(service.Identifier)
			resJ[service.Identifier] = joblist
			resC[service.Identifier] = config

			sub := 0
			for i := range resJ[service.Identifier].Details {
				if !resJ[service.Identifier].Details[i-sub].Running {
					resJ[service.Identifier].Details = append(resJ[service.Identifier].Details[:(i-sub)], resJ[service.Identifier].Details[(i-sub)+1:]...)
					sub++
				}
			}
		}
	}

	log.Printf("RETURNING FLEET STATUS: %v", resJ)
	return resJ, resC
}

// Find the first available server
func chooseServer(job *pbs.JobSpec, c checker) string {
	services := c.discover().Services
	for i := range rand.Perm(len(services)) {
		service := services[i]
		log.Printf("Trying %v", service)
		if service.Name == "gobuildslave" {
			jobs, sc := c.assess(service.Identifier)

			//Don't accept a server which is already running this job
			jobfine := true
			for _, j := range jobs.Details {
				log.Printf("%v and %v", job, j.Spec)
				if j.Spec.Name == job.Name {
					jobfine = false
				}
			}
			if jobfine {
				if sc.Disk > job.Disk {
					return service.Identifier
				}
			}
		}
	}
	return ""
}

func configDiff(cm, cs *pb.Config) *pb.Config {
	retConfig := &pb.Config{}

	log.Printf("COMPARE %v to %v", cm, cs)
	for _, entry := range cm.Intents {
		nIntent := &pb.Intent{}
		nIntent.Spec = entry.Spec
		nIntent.Count = entry.Count
		retConfig.Intents = append(retConfig.Intents, nIntent)
	}

	log.Printf("FIRST PASS: %v", retConfig)
	for _, entry := range cs.Intents {
		for _, pair := range retConfig.Intents {
			if entry.Spec.Name == pair.Spec.Name {
				log.Printf("FOUND: %v", pair)
				pair.Count -= entry.Count
				log.Printf("RESULT: %v", retConfig)
			}
		}
	}

	log.Printf("RETURNING: %v", retConfig)
	return retConfig
}

func loadConfig(f string) (*pb.Config, error) {
	toload := &pb.Config{}
	bytes, _ := ioutil.ReadFile(f)
	proto.UnmarshalText(string(bytes), toload)
	return toload, nil
}

func runJobs(c *pb.Config) []*pbs.JobSpec {
	var jobs []*pbs.JobSpec
	for _, j := range c.Intents {
		for i := 0; i < int(j.Count); i++ {
			jobs = append(jobs, j.Spec)
		}
	}

	return jobs
}
<commit_msg>More logging<commit_after>package main

import (
	"io/ioutil"
	"log"
	"math/rand"

	pb "github.com/brotherlogic/gobuildmaster/proto"
	pbs "github.com/brotherlogic/gobuildslave/proto"
	"github.com/golang/protobuf/proto"

	pbd "github.com/brotherlogic/discovery/proto"
)

type checker interface {
	assess(server string) (*pbs.JobList, *pbs.Config)
	discover() *pbd.ServiceList
}

func getFleetStatus(c checker) (map[string]*pbs.JobList, map[string]*pbs.Config) {
	resJ := make(map[string]*pbs.JobList)
	resC := make(map[string]*pbs.Config)

	for _, service := range c.discover().Services {
		log.Printf("Found: %v", service)
		if service.Name == "gobuildslave" {
			log.Printf("HERE: %v", service)
			joblist, config := c.assess(service.Identifier)
			resJ[service.Identifier] = joblist
			resC[service.Identifier] = config

			sub := 0
			for i := range resJ[service.Identifier].Details {
				if !resJ[service.Identifier].Details[i-sub].Running {
					resJ[service.Identifier].Details = append(resJ[service.Identifier].Details[:(i-sub)], resJ[service.Identifier].Details[(i-sub)+1:]...)
					sub++
				}
			}
		}
	}

	log.Printf("RETURNING FLEET STATUS: %v -> %v", resJ, resJ["framethree"])
	return resJ, resC
}

// Find the first available server
func chooseServer(job *pbs.JobSpec, c checker) string {
	services := c.discover().Services
	for i := range rand.Perm(len(services)) {
		service := services[i]
		log.Printf("Trying %v", service)
		if service.Name == "gobuildslave" {
			jobs, sc := c.assess(service.Identifier)

			//Don't accept a server which is already running this job
			jobfine := true
			for _, j := range jobs.Details {
				log.Printf("%v and %v", job, j.Spec)
				if j.Spec.Name == job.Name {
					jobfine = false
				}
			}
			if jobfine {
				if sc.Disk > job.Disk {
					return service.Identifier
				}
			}
		}
	}
	return ""
}

func configDiff(cm, cs *pb.Config) *pb.Config {
	retConfig := &pb.Config{}

	log.Printf("COMPARE %v to %v", cm, cs)
	for _, entry := range cm.Intents {
		nIntent := &pb.Intent{}
		nIntent.Spec = entry.Spec
		nIntent.Count = entry.Count
		retConfig.Intents = append(retConfig.Intents, nIntent)
	}

	log.Printf("FIRST PASS: %v", retConfig)
	for _, entry := range cs.Intents {
		for _, pair := range retConfig.Intents {
			if entry.Spec.Name == pair.Spec.Name {
				log.Printf("FOUND: %v", pair)
				pair.Count -= entry.Count
				log.Printf("RESULT: %v", retConfig)
			}
		}
	}

	log.Printf("RETURNING: %v", retConfig)
	return retConfig
}

func loadConfig(f string) (*pb.Config, error) {
	toload := &pb.Config{}
	bytes, _ := ioutil.ReadFile(f)
	proto.UnmarshalText(string(bytes), toload)
	return toload, nil
}

func runJobs(c *pb.Config) []*pbs.JobSpec {
	var jobs []*pbs.JobSpec
	for _, j := range c.Intents {
		for i := 0; i < int(j.Count); i++ {
			jobs = append(jobs, j.Spec)
		}
	}

	return jobs
}
<|endoftext|>


<commit_before>package chevalier

import (
	"github.com/anchor/elastigo/api"
	es "github.com/anchor/elastigo/core"
	"github.com/anchor/elastigo/search"

	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"
)

// QueryEngine presents an interface for running queries for sources
// against Elasticsearch.
type QueryEngine struct {
	indexName      string
	metaIndex      string
	originType     string
	dataType       string
	nSources       int64
	updateInterval time.Duration
}

// NewQueryEngine initializes a QueryEngine with the supplied
// Elasticsearch metadata. indexName and dataType can be anything as
// long as they're consistent.
func NewQueryEngine(host, indexName, dataType, metaIndex string) *QueryEngine {
	e := new(QueryEngine)
	e.indexName = indexName
	e.metaIndex = metaIndex
	e.dataType = dataType
	e.originType = "chevalier_origin"
	api.Domain = host
	e.updateSourceCount()
	e.updateInterval = time.Second * 10
	go e.updateForever()
	return e
}

// sanitizeField takes an input field specifier (from a
// SourceRequest_Tag) and munges it so Elasticsearch likes it more. In
// particular, it makes single-wildcard queries work correctly.
//
// FIXME: mid-field wildcards still aren't working.
func (e *QueryEngine) sanitizeField(f string) string {
	f = strings.TrimSpace(f)
	if f == "*" {
		return fmt.Sprintf("%s._all", e.dataType)
	}
	f = fmt.Sprintf("%s.%s", e.dataType, f)
	return f
}

// sanitizeTag escapes assorted Elasticsearch wildcards.
//
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters
func (e *QueryEngine) sanitizeTag(field, value string) (string, string) {
	// * is normally in this list, but is not included here because
	// we want it to act as a wildcard.
	// Also, this can be made a lot faster.
	reservedChars := `\ + - && || ! ( ) { } [ ] ^ " ~ ? : /`
	for _, char := range strings.Split(reservedChars, " ") {
		escapedChar := fmt.Sprintf(`\%s`, char)
		field = strings.Replace(field, char, escapedChar, -1)
		value = strings.Replace(value, char, escapedChar, -1)
	}
	field = e.sanitizeField(field)
	return field, value
}

// buildTagQuery constructs an elastigo query object (search.QueryDsl)
// from a SourceRequest_Tag, designed to be plugged into a
// query-string-type[0] query later on. Returns error on empty query.
//
// [0]: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
func (e *QueryEngine) buildTagQuery(tag *SourceRequest_Tag) (*search.QueryDsl, error) {
	field, value := e.sanitizeTag(*tag.Field, *tag.Value)
	// Don't bother running empty queries.
	if value == "" {
		return nil, errors.New("empty query string")
	}
	qs := new(search.QueryString)
	qs.Fields = make([]string, 0)
	qs.Fields = append(qs.Fields, field)
	qs.Query = value
	q := search.Query().Qs(qs)
	return q, nil
}

// buildOriginQuery returns the origin-matching part of the complete
// query.
func (e *QueryEngine) buildOriginQuery(origin string) *search.QueryDsl {
	qs := new(search.QueryString)
	qs.Fields = make([]string, 1)
	qs.Fields[0] = "Origin"
	qs.Query = origin
	q := search.Query().Qs(qs)
	return q
}

// SourceQuery is a multi-level map type representing an Elasticsearch
// query-string-type query. Suitable for marshalling as JSON and feeding
// to Elasticsearch.
type SourceQuery map[string]interface{}

func (e *QueryEngine) getStartResult(req *SourceRequest) int64 {
	startPage := req.GetStartPage()
	pageSize := req.GetSourcesPerPage()
	if pageSize == 0 {
		return int64(0)
	}
	return startPage * pageSize
}

// getResultCount returns the number of results to return for a
// request.
func (e *QueryEngine) getResultCount(req *SourceRequest) int64 {
	pageSize := req.GetSourcesPerPage()
	if pageSize <= 0 {
		return e.nSources
	}
	return pageSize
}

// BuildQuery takes a SourceRequest and turns it into a multi-level
// map suitable for marshalling to JSON and sending to Elasticsearch.
func (e *QueryEngine) BuildQuery(origin string, req *SourceRequest) (SourceQuery, error) {
	fromResult := e.getStartResult(req)
	resultCount := e.getResultCount(req)
	// First, we check if the Address field is set; if it is,
	// we can ignore the rest of the request.
	if req.Address != nil {
		query := map[string]interface{}{
			"query": map[string]interface{}{
				"term": map[string]uint64{
					"Address": *(req.Address),
				},
			},
			"from": fromResult,
			"size": resultCount,
		}
		return SourceQuery(query), nil
	}
	// Next, we check if the query_string field is set; if it is,
	// we can ignore the rest of the request.
	qs := req.GetQueryString()
	if qs != "" {
		query := map[string]interface{}{
			"query": map[string]interface{}{
				"query_string": map[string]interface{}{
					"query": qs,
				},
			},
			"from": fromResult,
			"size": resultCount,
		}
		return SourceQuery(query), nil
	}
	tags := req.GetTags()
	tagQueries := make([]*search.QueryDsl, 0)
	for _, tag := range tags {
		q, err := e.buildTagQuery(tag)
		if q != nil && err == nil {
			tagQueries = append(tagQueries, q)
		}
	}
	originTag := e.buildOriginQuery(origin)
	tagQueries = append(tagQueries, originTag)
	if len(tagQueries) == 0 {
		return nil, errors.New("No valid query strings found.")
	}
	query := map[string]interface{}{
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"must": tagQueries,
			},
		},
		"from": fromResult,
		"size": resultCount,
	}
	return SourceQuery(query), nil
}

// runSourceRequest takes a request object and returns an elastigo-type
// (i.e., intermediate) result.
func (e *QueryEngine) runSourceRequest(origin string, req *SourceRequest) (*es.SearchResult, error) {
	q, err := e.BuildQuery(origin, req)
	if err != nil {
		return nil, err
	}
	var args map[string]interface{}
	res, err := es.SearchRequest(e.indexName, e.dataType, args, q)
	return &res, err
}

// GetSources takes a request object and returns the DataSourceBurst of
// the sources it gets back from Elasticsearch. If error is not nil,
// then a valid DataSourceBurst will still be returned, with the Error
// field set.
func (e *QueryEngine) GetSources(origin string, req *SourceRequest) (*DataSourceBurst, error) {
	filterEmpty := true
	if req.GetIncludeEmpty() {
		filterEmpty = false
	}
	res, err := e.runSourceRequest(origin, req)
	if err != nil {
		msg := fmt.Sprintf("Request error: %v", err)
		burst := new(DataSourceBurst)
		burst.Error = &msg
		return burst, nil
	}
	sources := make([]*DataSource, 0)
	for _, hit := range res.Hits.Hits {
		source := new(ElasticsearchSource)
		err = json.Unmarshal(*hit.Source, source)
		if err != nil {
			msg := fmt.Sprintf("Response decoding error: %v", err)
			burst := new(DataSourceBurst)
			burst.Error = &msg
			return burst, nil
		}
		pbSource, err := source.Unmarshal()
		if err != nil {
			msg := fmt.Sprintf("Elasticsearch object decoding error: %v", err)
			burst := new(DataSourceBurst)
			burst.Error = &msg
			return burst, nil
		}
		// Include the source in the result set if it is not
		// empty, or the user has requested empty sources.
		if !pbSource.Empty() || filterEmpty == false {
			sources = append(sources, pbSource)
		}
	}
	burst := BuildSourceBurst(sources)
	return burst, nil
}

// FmtResult returns a string from a SearchResult by interpreting it in
// the most naive manner possible. For debugging.
func FmtResult(result *es.SearchResult) []string {
	results := make([]string, len(result.Hits.Hits))
	for i, hit := range result.Hits.Hits {
		results[i] = string((*hit.Source)[:])
	}
	return results
}

// GetSourceCount returns the number of sources we currently think exist
// in the index.
func (e *QueryEngine) GetSourceCount() int64 {
	return e.nSources
}

// updateSourceCount updates our running total of documents-in-index
// (by asking Elasticsearch).
func (e *QueryEngine) updateSourceCount() error {
	var args map[string]interface{}
	resp, err := es.Count(e.indexName, e.dataType, args)
	e.nSources = int64(resp.Count)
	return err
}

// updateForever updates the source counter on a regular basis.
func (e *QueryEngine) updateForever() {
	for {
		time.Sleep(e.updateInterval)
		err := e.updateSourceCount()
		if err != nil {
			log.Printf("Error updating source count: %v", err)
		}
	}
}
<commit_msg>Terser negative test<commit_after>package chevalier

import (
	"github.com/anchor/elastigo/api"
	es "github.com/anchor/elastigo/core"
	"github.com/anchor/elastigo/search"

	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"
)

// QueryEngine presents an interface for running queries for sources
// against Elasticsearch.
type QueryEngine struct {
	indexName      string
	metaIndex      string
	originType     string
	dataType       string
	nSources       int64
	updateInterval time.Duration
}

// NewQueryEngine initializes a QueryEngine with the supplied
// Elasticsearch metadata. indexName and dataType can be anything as
// long as they're consistent.
func NewQueryEngine(host, indexName, dataType, metaIndex string) *QueryEngine {
	e := new(QueryEngine)
	e.indexName = indexName
	e.metaIndex = metaIndex
	e.dataType = dataType
	e.originType = "chevalier_origin"
	api.Domain = host
	e.updateSourceCount()
	e.updateInterval = time.Second * 10
	go e.updateForever()
	return e
}

// sanitizeField takes an input field specifier (from a
// SourceRequest_Tag) and munges it so Elasticsearch likes it more. In
// particular, it makes single-wildcard queries work correctly.
//
// FIXME: mid-field wildcards still aren't working.
func (e *QueryEngine) sanitizeField(f string) string {
	f = strings.TrimSpace(f)
	if f == "*" {
		return fmt.Sprintf("%s._all", e.dataType)
	}
	f = fmt.Sprintf("%s.%s", e.dataType, f)
	return f
}

// sanitizeTag escapes assorted Elasticsearch wildcards.
//
// http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#_reserved_characters
func (e *QueryEngine) sanitizeTag(field, value string) (string, string) {
	// * is normally in this list, but is not included here because
	// we want it to act as a wildcard.
	// Also, this can be made a lot faster.
	reservedChars := `\ + - && || ! ( ) { } [ ] ^ " ~ ? : /`
	for _, char := range strings.Split(reservedChars, " ") {
		escapedChar := fmt.Sprintf(`\%s`, char)
		field = strings.Replace(field, char, escapedChar, -1)
		value = strings.Replace(value, char, escapedChar, -1)
	}
	field = e.sanitizeField(field)
	return field, value
}

// buildTagQuery constructs an elastigo query object (search.QueryDsl)
// from a SourceRequest_Tag, designed to be plugged into a
// query-string-type[0] query later on. Returns error on empty query.
//
// [0]: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html
func (e *QueryEngine) buildTagQuery(tag *SourceRequest_Tag) (*search.QueryDsl, error) {
	field, value := e.sanitizeTag(*tag.Field, *tag.Value)
	// Don't bother running empty queries.
	if value == "" {
		return nil, errors.New("empty query string")
	}
	qs := new(search.QueryString)
	qs.Fields = make([]string, 0)
	qs.Fields = append(qs.Fields, field)
	qs.Query = value
	q := search.Query().Qs(qs)
	return q, nil
}

// buildOriginQuery returns the origin-matching part of the complete
// query.
func (e *QueryEngine) buildOriginQuery(origin string) *search.QueryDsl {
	qs := new(search.QueryString)
	qs.Fields = make([]string, 1)
	qs.Fields[0] = "Origin"
	qs.Query = origin
	q := search.Query().Qs(qs)
	return q
}

// SourceQuery is a multi-level map type representing an Elasticsearch
// query-string-type query. Suitable for marshalling as JSON and feeding
// to Elasticsearch.
type SourceQuery map[string]interface{}

func (e *QueryEngine) getStartResult(req *SourceRequest) int64 {
	startPage := req.GetStartPage()
	pageSize := req.GetSourcesPerPage()
	if pageSize == 0 {
		return int64(0)
	}
	return startPage * pageSize
}

// getResultCount returns the number of results to return for a
// request.
func (e *QueryEngine) getResultCount(req *SourceRequest) int64 {
	pageSize := req.GetSourcesPerPage()
	if pageSize <= 0 {
		return e.nSources
	}
	return pageSize
}

// BuildQuery takes a SourceRequest and turns it into a multi-level
// map suitable for marshalling to JSON and sending to Elasticsearch.
func (e *QueryEngine) BuildQuery(origin string, req *SourceRequest) (SourceQuery, error) {
	fromResult := e.getStartResult(req)
	resultCount := e.getResultCount(req)
	// First, we check if the Address field is set; if it is,
	// we can ignore the rest of the request.
	if req.Address != nil {
		query := map[string]interface{}{
			"query": map[string]interface{}{
				"term": map[string]uint64{
					"Address": *(req.Address),
				},
			},
			"from": fromResult,
			"size": resultCount,
		}
		return SourceQuery(query), nil
	}
	// Next, we check if the query_string field is set; if it is,
	// we can ignore the rest of the request.
	qs := req.GetQueryString()
	if qs != "" {
		query := map[string]interface{}{
			"query": map[string]interface{}{
				"query_string": map[string]interface{}{
					"query": qs,
				},
			},
			"from": fromResult,
			"size": resultCount,
		}
		return SourceQuery(query), nil
	}
	tags := req.GetTags()
	tagQueries := make([]*search.QueryDsl, 0)
	for _, tag := range tags {
		q, err := e.buildTagQuery(tag)
		if q != nil && err == nil {
			tagQueries = append(tagQueries, q)
		}
	}
	originTag := e.buildOriginQuery(origin)
	tagQueries = append(tagQueries, originTag)
	if len(tagQueries) == 0 {
		return nil, errors.New("No valid query strings found.")
	}
	query := map[string]interface{}{
		"query": map[string]interface{}{
			"bool": map[string]interface{}{
				"must": tagQueries,
			},
		},
		"from": fromResult,
		"size": resultCount,
	}
	return SourceQuery(query), nil
}

// runSourceRequest takes a request object and returns an elastigo-type
// (i.e., intermediate) result.
func (e *QueryEngine) runSourceRequest(origin string, req *SourceRequest) (*es.SearchResult, error) {
	q, err := e.BuildQuery(origin, req)
	if err != nil {
		return nil, err
	}
	var args map[string]interface{}
	res, err := es.SearchRequest(e.indexName, e.dataType, args, q)
	return &res, err
}

// GetSources takes a request object and returns the DataSourceBurst of
// the sources it gets back from Elasticsearch. If error is not nil,
// then a valid DataSourceBurst will still be returned, with the Error
// field set.
func (e *QueryEngine) GetSources(origin string, req *SourceRequest) (*DataSourceBurst, error) {
	filterEmpty := true
	if req.GetIncludeEmpty() {
		filterEmpty = false
	}
	res, err := e.runSourceRequest(origin, req)
	if err != nil {
		msg := fmt.Sprintf("Request error: %v", err)
		burst := new(DataSourceBurst)
		burst.Error = &msg
		return burst, nil
	}
	sources := make([]*DataSource, 0)
	for _, hit := range res.Hits.Hits {
		source := new(ElasticsearchSource)
		err = json.Unmarshal(*hit.Source, source)
		if err != nil {
			msg := fmt.Sprintf("Response decoding error: %v", err)
			burst := new(DataSourceBurst)
			burst.Error = &msg
			return burst, nil
		}
		pbSource, err := source.Unmarshal()
		if err != nil {
			msg := fmt.Sprintf("Elasticsearch object decoding error: %v", err)
			burst := new(DataSourceBurst)
			burst.Error = &msg
			return burst, nil
		}
		// Include the source in the result set if it is not
		// empty, or the user has requested empty sources.
		if !pbSource.Empty() || !filterEmpty {
			sources = append(sources, pbSource)
		}
	}
	burst := BuildSourceBurst(sources)
	return burst, nil
}

// FmtResult returns a string from a SearchResult by interpreting it in
// the most naive manner possible. For debugging.
func FmtResult(result *es.SearchResult) []string {
	results := make([]string, len(result.Hits.Hits))
	for i, hit := range result.Hits.Hits {
		results[i] = string((*hit.Source)[:])
	}
	return results
}

// GetSourceCount returns the number of sources we currently think exist
// in the index.
func (e *QueryEngine) GetSourceCount() int64 {
	return e.nSources
}

// updateSourceCount updates our running total of documents-in-index
// (by asking Elasticsearch).
func (e *QueryEngine) updateSourceCount() error {
	var args map[string]interface{}
	resp, err := es.Count(e.indexName, e.dataType, args)
	e.nSources = int64(resp.Count)
	return err
}

// updateForever updates the source counter on a regular basis.
func (e *QueryEngine) updateForever() {
	for {
		time.Sleep(e.updateInterval)
		err := e.updateSourceCount()
		if err != nil {
			log.Printf("Error updating source count: %v", err)
		}
	}
}
<|endoftext|>


<commit_before>package tmdb

import (
	"fmt"
	"net/url"
	"encoding/json"
	"errors"
)

var ErrUnknownMediaType  = errors.New("Unknown media type. Unable to unmarhal to MultiSearchResultsInfo")

// CollectionSearchResults struct
type CollectionSearchResults struct {
	Page    int
	Results []struct {
		ID           int
		BackdropPath string `json:"backdrop_path"`
		Name         string
		PosterPath   string `json:"poster_path"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// CompanySearchResults struct
type CompanySearchResults struct {
	Page    int
	Results []struct {
		ID       int
		LogoPath string `json:"logo_path"`
		Name     string
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// KeywordSearchResults struct
type KeywordSearchResults struct {
	Page    int
	Results []struct {
		ID   int
		Name string
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// ListSearchResults struct
type ListSearchResults struct {
	Page    int
	Results []struct {
		Description   string
		FavoriteCount int `json:"favorite_count"`
		ID            string
		ItemCount     int    `json:"item_count"`
		Iso639_1      string `json:"iso_639_1"`
		ListType      string `json:"list_type"`
		Name          string
		PosterPath    string `json:"poster_path"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// MovieSearchResults struct
type MovieSearchResults struct {
	Page         int
	Results      []MovieShort
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

type MultiSearchBase interface {
	interfaceMarkerMethod()  // We need a method to be
}

type MultiSearchMovieInfo struct {
	PosterPath		string  	`json:"poster_path"`
	Adult			bool
	Overview        	string  	`json:"overview"`
	ReleaseDate		string 		`json:"release_date"`
	OriginalTitle  		string  	`json:"original_title"`
	GenreIDs		[]uint32 	`json:"ganre_ids"`
	OriginalLanguage 	string   	`json:"original_language"`
	Title  			string  	`json:"title"`
	BackdropPath  		string 		`json:"backdrop_path"`
	Popularity    		float32
	VoteCount     		uint32  	`json:"vote_count"`
	Video			bool
	VoteAverage 		float32  	`json:"vote_average"`
	ID         	 int
	MediaType     	 string  `json:"media_type"`
}

func (MultiSearchMovieInfo)interfaceMarkerMethod() {return }

type MultiSearchTvInfo struct {
	BackdropPath  	 string `json:"backdrop_path"`
	OriginalName  	 string   `json:"original_name"`
	OriginalTitle 	 string   `json:"original_title"`
	OriginalLanguage string   	`json:"original_language"`
	Overview      	 string   `json:"overview"`
	FirstAirDate     string   `json:"first_air_date"`
	OriginCountry 	 []string `json:"origin_country"`
	GenreIDs      	 []uint32 `json:"ganre_ids"`
	PosterPath       string   `json:"poster_path"`
	Popularity    	 float32
	Name          	 string
	VoteAverage   	 float32 `json:"vote_average"`
	VoteCount     	 uint32  `json:"vote_count"`
	ID         	 int
	MediaType     	 string  `json:"media_type"`

}

func (MultiSearchTvInfo)interfaceMarkerMethod() {return }

type MultiSearchPersonInfo struct {
	ProfilePath  	 string `json:"profile_path"`
	Adult		 bool
	KnownFor	 MultiSearchResultsInfo
	ID         	 int
	MediaType     	 string  `json:"media_type"`
}

func (MultiSearchPersonInfo) interfaceMarkerMethod() {return }

func (res MultiSearchPersonInfo) GetMoviesKnownFor() (movieResults []MultiSearchMovieInfo) {
	movieResults = make([]MultiSearchMovieInfo,0);

	for i := 0; i < len(res.KnownFor);i++ {
		var base interface{}= res.KnownFor[i]
		if casted,ok := base.(*MultiSearchMovieInfo); ok {
			movieResults = append(movieResults, *casted)
		}
	}

	return;
}

func (res MultiSearchPersonInfo) GetTvKnownFor() (tvResults []MultiSearchTvInfo) {
	tvResults = make([]MultiSearchTvInfo,0);

	for i := 0; i < len(res.KnownFor);i++ {
		var base interface{}= res.KnownFor[i]
		if casted,ok := base.(*MultiSearchTvInfo); ok {
			tvResults = append(tvResults, *casted)
		}
	}

	return;
}
type MultiSearchResultsInfo []MultiSearchBase

func (v *MultiSearchResultsInfo) UnmarshalJSON(data []byte) error {
	//splits up the JSON array into the raw JSON for each object
	var raw []json.RawMessage
	err := json.Unmarshal(data, &raw)
	if err != nil {
		return err
	}
	for _, r := range raw {
		// unamrshal into a map to check the "type" field
		var obj map[string]interface{}
		err := json.Unmarshal(r, &obj)
		if err != nil {
			return err
		}

		mediaType := ""
		if t, ok := obj["media_type"].(string); ok {
			mediaType = t
		}

		// unmarshal again into the correct type
		var actual MultiSearchBase
		switch mediaType {
		case "movie":
			actual = &MultiSearchMovieInfo{}
		case "tv":
			actual = &MultiSearchTvInfo{}
		case "person":
			actual = &MultiSearchPersonInfo{}
		default:
			return ErrUnknownMediaType
		}

		err = json.Unmarshal(r, actual)
		if err != nil {
			return err
		}
		*v = append(*v, actual)
	}
	return nil;
}
// MultiSearchResults struct
type MultiSearchResults struct {
	Page    int
	Results MultiSearchResultsInfo
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

func (res MultiSearchResults) GetMoviesResults() (movieResults []MultiSearchMovieInfo) {

	for i := 0; i < len(res.Results);i++ {
		var base interface{}= res.Results[i]
		if casted,ok := base.(*MultiSearchMovieInfo); ok {
			movieResults = append(movieResults, *casted)
		}
	}

	return;
}

func (res MultiSearchResults) GetTvResults() (tvResults []MultiSearchTvInfo) {

	for i := 0; i < len(res.Results);i++ {
		var base interface{}= res.Results[i]

		if casted,ok := base.(*MultiSearchTvInfo); ok {
			tvResults = append(tvResults, *casted)
		}
	}

	return;
}

func (res MultiSearchResults) GetPersonResults() (personResults []MultiSearchPersonInfo) {

	for i := 0; i < len(res.Results);i++ {
		var base interface{}= res.Results[i]
		if casted,ok := base.(*MultiSearchPersonInfo); ok {
			personResults = append(personResults, *casted)
		}
	}

	return;
}

// PersonSearchResults struct
type PersonSearchResults struct {
	Page    int
	Results []struct {
		Adult      bool
		ID         int
		Name       string
		Popularity float32
		PosterPath string `json:"poster_path"`
		KnownFor   []struct {
			Adult         bool
			BackdropPath  string `json:"backdrop_path"`
			ID            int
			OriginalTitle string `json:"original_title"`
			ReleaseDate   string `json:"release_date"`
			PosterPath    string `json:"poster_path"`
			Popularity    float32
			Title         string
			VoteAverage   float32 `json:"vote_average"`
			VoteCount     uint32  `json:"vote_count"`
			MediaType     string  `json:"media_type"`
		} `json:"known_for"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// TvSearchResults struct
type TvSearchResults struct {
	Page    int
	Results []struct {
		BackdropPath  string `json:"backdrop_path"`
		ID            int
		OriginalName  string   `json:"original_name"`
		FirstAirDate  string   `json:"first_air_date"`
		OriginCountry []string `json:"origin_country"`
		PosterPath    string   `json:"poster_path"`
		Popularity    float32
		Name          string
		VoteAverage   float32 `json:"vote_average"`
		VoteCount     uint32  `json:"vote_count"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// SearchCollection searches for collections by name
// http://docs.themoviedb.apiary.io/#reference/search/searchcollection/get
func (tmdb *TMDb) SearchCollection(name string, options map[string]string) (*CollectionSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":     {},
		"language": {}}
	var collections CollectionSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/collection?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &collections)
	return result.(*CollectionSearchResults), err
}

// SearchCompany searches for companies by name
// http://docs.themoviedb.apiary.io/#reference/search/searchcompany/get
func (tmdb *TMDb) SearchCompany(name string, options map[string]string) (*CompanySearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page": {}}
	var companies CompanySearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/company?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &companies)
	return result.(*CompanySearchResults), err
}

// SearchKeyword searches for keywords by name
// http://docs.themoviedb.apiary.io/#reference/search/searchkeyword/get
func (tmdb *TMDb) SearchKeyword(name string, options map[string]string) (*KeywordSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page": {}}
	var keywords KeywordSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/keyword?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &keywords)
	return result.(*KeywordSearchResults), err
}

// SearchList searches for lists by name and description
// http://docs.themoviedb.apiary.io/#reference/search/searchlist/get
func (tmdb *TMDb) SearchList(name string, options map[string]string) (*ListSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":          {},
		"include_adult": {}}
	var lists ListSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/list?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &lists)
	return result.(*ListSearchResults), err
}

// SearchMovie searches for movies by title
// http://docs.themoviedb.apiary.io/#reference/search/searchmovie/get
func (tmdb *TMDb) SearchMovie(name string, options map[string]string) (*MovieSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":                 {},
		"language":             {},
		"include_adult":        {},
		"year":                 {},
		"primary_release_year": {},
		"search_type":          {}}
	var movies MovieSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/movie?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &movies)
	return result.(*MovieSearchResults), err
}

// SearchMulti searches the movie, tv show and person collections with a single query
// http://docs.themoviedb.apiary.io/#reference/search/searchmulti/get
func (tmdb *TMDb) SearchMulti(name string, options map[string]string) (*MultiSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":          {},
		"language":      {},
		"include_adult": {}}
	var multis MultiSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/multi?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &multis)
	return result.(*MultiSearchResults), err
}

// SearchPerson searches for people by name
// http://docs.themoviedb.apiary.io/#reference/search/searchperson/get
func (tmdb *TMDb) SearchPerson(name string, options map[string]string) (*PersonSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":          {},
		"search_type":   {},
		"include_adult": {}}
	var people PersonSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/person?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &people)
	return result.(*PersonSearchResults), err
}

// SearchTv searches for TV shows by title
// http://docs.themoviedb.apiary.io/#reference/search/searchtv/get
func (tmdb *TMDb) SearchTv(name string, options map[string]string) (*TvSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":                {},
		"language":            {},
		"search_type":         {},
		"first_air_date_year": {}}
	var shows TvSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/tv?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &shows)
	return result.(*TvSearchResults), err
}
<commit_msg>add `known_for` tag for person in multi search (#18)<commit_after>package tmdb

import (
	"fmt"
	"net/url"
	"encoding/json"
	"errors"
)

var ErrUnknownMediaType  = errors.New("Unknown media type. Unable to unmarhal to MultiSearchResultsInfo")

// CollectionSearchResults struct
type CollectionSearchResults struct {
	Page    int
	Results []struct {
		ID           int
		BackdropPath string `json:"backdrop_path"`
		Name         string
		PosterPath   string `json:"poster_path"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// CompanySearchResults struct
type CompanySearchResults struct {
	Page    int
	Results []struct {
		ID       int
		LogoPath string `json:"logo_path"`
		Name     string
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// KeywordSearchResults struct
type KeywordSearchResults struct {
	Page    int
	Results []struct {
		ID   int
		Name string
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// ListSearchResults struct
type ListSearchResults struct {
	Page    int
	Results []struct {
		Description   string
		FavoriteCount int `json:"favorite_count"`
		ID            string
		ItemCount     int    `json:"item_count"`
		Iso639_1      string `json:"iso_639_1"`
		ListType      string `json:"list_type"`
		Name          string
		PosterPath    string `json:"poster_path"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// MovieSearchResults struct
type MovieSearchResults struct {
	Page         int
	Results      []MovieShort
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

type MultiSearchBase interface {
	interfaceMarkerMethod()  // We need a method to be
}

type MultiSearchMovieInfo struct {
	PosterPath		string  	`json:"poster_path"`
	Adult			bool
	Overview        	string  	`json:"overview"`
	ReleaseDate		string 		`json:"release_date"`
	OriginalTitle  		string  	`json:"original_title"`
	GenreIDs		[]uint32 	`json:"ganre_ids"`
	OriginalLanguage 	string   	`json:"original_language"`
	Title  			string  	`json:"title"`
	BackdropPath  		string 		`json:"backdrop_path"`
	Popularity    		float32
	VoteCount     		uint32  	`json:"vote_count"`
	Video			bool
	VoteAverage 		float32  	`json:"vote_average"`
	ID         	 int
	MediaType     	 string  `json:"media_type"`
}

func (MultiSearchMovieInfo)interfaceMarkerMethod() {return }

type MultiSearchTvInfo struct {
	BackdropPath  	 string `json:"backdrop_path"`
	OriginalName  	 string   `json:"original_name"`
	OriginalTitle 	 string   `json:"original_title"`
	OriginalLanguage string   	`json:"original_language"`
	Overview      	 string   `json:"overview"`
	FirstAirDate     string   `json:"first_air_date"`
	OriginCountry 	 []string `json:"origin_country"`
	GenreIDs      	 []uint32 `json:"ganre_ids"`
	PosterPath       string   `json:"poster_path"`
	Popularity    	 float32
	Name          	 string
	VoteAverage   	 float32 `json:"vote_average"`
	VoteCount     	 uint32  `json:"vote_count"`
	ID         	 int
	MediaType     	 string  `json:"media_type"`

}

func (MultiSearchTvInfo)interfaceMarkerMethod() {return }

type MultiSearchPersonInfo struct {
	ProfilePath  	 string `json:"profile_path"`
	Adult		 bool
	KnownFor	 MultiSearchResultsInfo `json:"known_for"`
	ID         	 int
	MediaType     	 string  `json:"media_type"`
}

func (MultiSearchPersonInfo) interfaceMarkerMethod() {return }

func (res MultiSearchPersonInfo) GetMoviesKnownFor() (movieResults []MultiSearchMovieInfo) {
	movieResults = make([]MultiSearchMovieInfo,0);

	for i := 0; i < len(res.KnownFor);i++ {
		var base interface{}= res.KnownFor[i]
		if casted,ok := base.(*MultiSearchMovieInfo); ok {
			movieResults = append(movieResults, *casted)
		}
	}

	return;
}

func (res MultiSearchPersonInfo) GetTvKnownFor() (tvResults []MultiSearchTvInfo) {
	tvResults = make([]MultiSearchTvInfo,0);

	for i := 0; i < len(res.KnownFor);i++ {
		var base interface{}= res.KnownFor[i]
		if casted,ok := base.(*MultiSearchTvInfo); ok {
			tvResults = append(tvResults, *casted)
		}
	}

	return;
}
type MultiSearchResultsInfo []MultiSearchBase

func (v *MultiSearchResultsInfo) UnmarshalJSON(data []byte) error {
	//splits up the JSON array into the raw JSON for each object
	var raw []json.RawMessage
	err := json.Unmarshal(data, &raw)
	if err != nil {
		return err
	}
	for _, r := range raw {
		// unamrshal into a map to check the "type" field
		var obj map[string]interface{}
		err := json.Unmarshal(r, &obj)
		if err != nil {
			return err
		}

		mediaType := ""
		if t, ok := obj["media_type"].(string); ok {
			mediaType = t
		}

		// unmarshal again into the correct type
		var actual MultiSearchBase
		switch mediaType {
		case "movie":
			actual = &MultiSearchMovieInfo{}
		case "tv":
			actual = &MultiSearchTvInfo{}
		case "person":
			actual = &MultiSearchPersonInfo{}
		default:
			return ErrUnknownMediaType
		}

		err = json.Unmarshal(r, actual)
		if err != nil {
			return err
		}
		*v = append(*v, actual)
	}
	return nil;
}
// MultiSearchResults struct
type MultiSearchResults struct {
	Page    int
	Results MultiSearchResultsInfo
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

func (res MultiSearchResults) GetMoviesResults() (movieResults []MultiSearchMovieInfo) {

	for i := 0; i < len(res.Results);i++ {
		var base interface{}= res.Results[i]
		if casted,ok := base.(*MultiSearchMovieInfo); ok {
			movieResults = append(movieResults, *casted)
		}
	}

	return;
}

func (res MultiSearchResults) GetTvResults() (tvResults []MultiSearchTvInfo) {

	for i := 0; i < len(res.Results);i++ {
		var base interface{}= res.Results[i]

		if casted,ok := base.(*MultiSearchTvInfo); ok {
			tvResults = append(tvResults, *casted)
		}
	}

	return;
}

func (res MultiSearchResults) GetPersonResults() (personResults []MultiSearchPersonInfo) {

	for i := 0; i < len(res.Results);i++ {
		var base interface{}= res.Results[i]
		if casted,ok := base.(*MultiSearchPersonInfo); ok {
			personResults = append(personResults, *casted)
		}
	}

	return;
}

// PersonSearchResults struct
type PersonSearchResults struct {
	Page    int
	Results []struct {
		Adult      bool
		ID         int
		Name       string
		Popularity float32
		PosterPath string `json:"poster_path"`
		KnownFor   []struct {
			Adult         bool
			BackdropPath  string `json:"backdrop_path"`
			ID            int
			OriginalTitle string `json:"original_title"`
			ReleaseDate   string `json:"release_date"`
			PosterPath    string `json:"poster_path"`
			Popularity    float32
			Title         string
			VoteAverage   float32 `json:"vote_average"`
			VoteCount     uint32  `json:"vote_count"`
			MediaType     string  `json:"media_type"`
		} `json:"known_for"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// TvSearchResults struct
type TvSearchResults struct {
	Page    int
	Results []struct {
		BackdropPath  string `json:"backdrop_path"`
		ID            int
		OriginalName  string   `json:"original_name"`
		FirstAirDate  string   `json:"first_air_date"`
		OriginCountry []string `json:"origin_country"`
		PosterPath    string   `json:"poster_path"`
		Popularity    float32
		Name          string
		VoteAverage   float32 `json:"vote_average"`
		VoteCount     uint32  `json:"vote_count"`
	}
	TotalPages   int `json:"total_pages"`
	TotalResults int `json:"total_results"`
}

// SearchCollection searches for collections by name
// http://docs.themoviedb.apiary.io/#reference/search/searchcollection/get
func (tmdb *TMDb) SearchCollection(name string, options map[string]string) (*CollectionSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":     {},
		"language": {}}
	var collections CollectionSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/collection?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &collections)
	return result.(*CollectionSearchResults), err
}

// SearchCompany searches for companies by name
// http://docs.themoviedb.apiary.io/#reference/search/searchcompany/get
func (tmdb *TMDb) SearchCompany(name string, options map[string]string) (*CompanySearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page": {}}
	var companies CompanySearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/company?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &companies)
	return result.(*CompanySearchResults), err
}

// SearchKeyword searches for keywords by name
// http://docs.themoviedb.apiary.io/#reference/search/searchkeyword/get
func (tmdb *TMDb) SearchKeyword(name string, options map[string]string) (*KeywordSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page": {}}
	var keywords KeywordSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/keyword?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &keywords)
	return result.(*KeywordSearchResults), err
}

// SearchList searches for lists by name and description
// http://docs.themoviedb.apiary.io/#reference/search/searchlist/get
func (tmdb *TMDb) SearchList(name string, options map[string]string) (*ListSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":          {},
		"include_adult": {}}
	var lists ListSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/list?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &lists)
	return result.(*ListSearchResults), err
}

// SearchMovie searches for movies by title
// http://docs.themoviedb.apiary.io/#reference/search/searchmovie/get
func (tmdb *TMDb) SearchMovie(name string, options map[string]string) (*MovieSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":                 {},
		"language":             {},
		"include_adult":        {},
		"year":                 {},
		"primary_release_year": {},
		"search_type":          {}}
	var movies MovieSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/movie?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &movies)
	return result.(*MovieSearchResults), err
}

// SearchMulti searches the movie, tv show and person collections with a single query
// http://docs.themoviedb.apiary.io/#reference/search/searchmulti/get
func (tmdb *TMDb) SearchMulti(name string, options map[string]string) (*MultiSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":          {},
		"language":      {},
		"include_adult": {}}
	var multis MultiSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/multi?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &multis)
	return result.(*MultiSearchResults), err
}

// SearchPerson searches for people by name
// http://docs.themoviedb.apiary.io/#reference/search/searchperson/get
func (tmdb *TMDb) SearchPerson(name string, options map[string]string) (*PersonSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":          {},
		"search_type":   {},
		"include_adult": {}}
	var people PersonSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/person?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &people)
	return result.(*PersonSearchResults), err
}

// SearchTv searches for TV shows by title
// http://docs.themoviedb.apiary.io/#reference/search/searchtv/get
func (tmdb *TMDb) SearchTv(name string, options map[string]string) (*TvSearchResults, error) {
	var availableOptions = map[string]struct{}{
		"page":                {},
		"language":            {},
		"search_type":         {},
		"first_air_date_year": {}}
	var shows TvSearchResults
	safeName := url.QueryEscape(name)
	optionsString := getOptionsString(options, availableOptions)
	uri := fmt.Sprintf("%s/search/tv?query=%s&api_key=%s%s", baseURL, safeName, tmdb.apiKey, optionsString)
	result, err := getTmdb(uri, &shows)
	return result.(*TvSearchResults), err
}
<|endoftext|>


<commit_before>package cuckoo

const MAX_SEARCH_DEPTH int = 10

type mv struct {
	key  keyt
	from int
	to   int
	tobn int
}

func (m *cmap) search(bins ...int) []mv {
	for depth := 1; depth < MAX_SEARCH_DEPTH; depth++ {
		for _, b := range bins {
			path := m.find(nil, b, depth)
			if path != nil {
				return path
			}
		}
	}
	return nil
}

func (m *cmap) find(path []mv, bin int, depth int) []mv {
	if depth >= 0 {
		for i := 0; i < ASSOCIATIVITY; i++ {
			if !m.bins[bin].vals[i].present() {
				return path
			}

			path_ := make([]mv, len(path)+1)
			for i := range path {
				path_[i] = path[i]
			}

			from := bin
			to := from
			bno := m.bins[bin].vals[i].bno + 1
			key := m.bins[bin].vals[i].key
			for i := 0; i < int(m.hashes); i++ {
				bno = (bno + 1) % int(m.hashes)
				to = m.bin(bno, key)
				// XXX: could potentially try all bins here and
				// check each for available()? extra-broad
				// search...
				if to != from {
					break
				}
			}
			if to == from {
				continue
			}

			path_[len(path)] = mv{key, from, to, bno}
			if m.bins[to].available() {
				return path_
			} else {
				return m.find(path_, to, depth-1)
			}
		}
	}
	return nil
}
<commit_msg>Skip choices with cycles<commit_after>package cuckoo

const MAX_SEARCH_DEPTH int = 10

type mv struct {
	key  keyt
	from int
	to   int
	tobn int
}

func (m *cmap) search(bins ...int) []mv {
	for depth := 1; depth < MAX_SEARCH_DEPTH; depth++ {
		for _, b := range bins {
			path := m.find(nil, b, depth)
			if path != nil {
				return path
			}
		}
	}
	return nil
}

func (m *cmap) find(path []mv, bin int, depth int) []mv {
	if depth >= 0 {
		for i := 0; i < ASSOCIATIVITY; i++ {
			if !m.bins[bin].vals[i].present() {
				return path
			}

			path_ := make([]mv, len(path)+1)
			for i := range path {
				path_[i] = path[i]
			}

			from := bin
			to := from
			bno := m.bins[bin].vals[i].bno + 1
			key := m.bins[bin].vals[i].key
			for i := 0; i < int(m.hashes); i++ {
				bno = (bno + 1) % int(m.hashes)
				to = m.bin(bno, key)
				// XXX: could potentially try all bins here and
				// check each for available()? extra-broad
				// search...
				if to != from {
					break
				}
			}
			if to == from {
				continue
			}

			skip := false
			for _, p := range path {
				if p.from == to {
					skip = true
					break
				}
			}

			if skip {
				// XXX: could instead try next bin here
				continue
			}

			path_[len(path)] = mv{key, from, to, bno}
			if m.bins[to].available() {
				return path_
			} else {
				return m.find(path_, to, depth-1)
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package google

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccLoggingBucketConfigFolder_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix": randString(t, 10),
		"folder_name":   "tf-test-" + randString(t, 10),
		"org_id":        getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigFolder_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_folder_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"folder"},
			},
			{
				Config: testAccLoggingBucketConfigFolder_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_folder_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"folder"},
			},
		},
	})
}

func TestAccLoggingBucketConfigProject_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix": randString(t, 10),
		"project_name":  "tf-test-" + randString(t, 10),
		"org_id":        getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigProject_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_project_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"project"},
			},
			{
				Config: testAccLoggingBucketConfigProject_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_project_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"project"},
			},
		},
	})
}

func TestAccLoggingBucketConfigBillingAccount_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix":        randString(t, 10),
		"billing_account_name": "billingAccounts/" + getTestBillingAccountFromEnv(t),
		"org_id":               getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigBillingAccount_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_billing_account_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"billing_account"},
			},
			{
				Config: testAccLoggingBucketConfigBillingAccount_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_billing_account_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"billing_account"},
			},
		},
	})
}

func TestAccLoggingBucketConfigOrganization_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix": randString(t, 10),
		"org_id":        getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigOrganization_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_organization_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"organization"},
			},
			{
				Config: testAccLoggingBucketConfigOrganization_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_organization_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"organization"},
			},
		},
	})
}

func testAccLoggingBucketConfigFolder_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`
resource "google_folder" "default" {
	display_name = "%{folder_name}"
	parent       = "organizations/%{org_id}"
}

resource "google_logging_folder_bucket_config" "basic" {
	folder    = google_folder.default.name
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func testAccLoggingBucketConfigProject_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`
resource "google_project" "default" {
	project_id = "%{project_name}"
	name       = "%{project_name}"
	org_id     = "%{org_id}"
}

resource "google_logging_project_bucket_config" "basic" {
	project    = google_project.default.name
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func TestAccLoggingBucketConfig_CreateBuckets_withCustomId(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix":        randString(t, 10),
		"billing_account_name": "billingAccounts/" + getTestBillingAccountFromEnv(t),
		"org_id":               getTestOrgFromEnv(t),
		"project_name":         "tf-test-" + randString(t, 10),
		"bucket_id":            "tf-test-bucket-" + randString(t, 10),
	}

	configList := getLoggingBucketConfigs(context)

	for res, config := range configList {
		vcrTest(t, resource.TestCase{
			PreCheck:  func() { testAccPreCheck(t) },
			Providers: testAccProviders,
			Steps: []resource.TestStep{
				{
					Config: config,
				},
				{
					ResourceName:            fmt.Sprintf("google_logging_%s_bucket_config.basic", res),
					ImportState:             true,
					ImportStateVerify:       true,
					ImportStateVerifyIgnore: []string{res},
				},
			},
		})
	}
}

func testAccLoggingBucketConfigBillingAccount_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`

data "google_billing_account" "default" {
	billing_account = "%{billing_account_name}"
}

resource "google_logging_billing_account_bucket_config" "basic" {
	billing_account    = data.google_billing_account.default.billing_account
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func testAccLoggingBucketConfigOrganization_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`
data "google_organization" "default" {
	organization = "%{org_id}"
}

resource "google_logging_organization_bucket_config" "basic" {
	organization    = data.google_organization.default.organization
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func getLoggingBucketConfigs(context map[string]interface{}) map[string]string {

	return map[string]string{
		"project": Nprintf(`resource "google_project" "default" {
				project_id = "%{project_name}"
				name       = "%{project_name}"
				org_id     = "%{org_id}"
			}

			resource "google_logging_project_bucket_config" "basic" {
				project    = google_project.default.name
				location  = "global"
				retention_days = 10
				description = "retention test 10 days"
				bucket_id = "%{bucket_id}"
			}`, context),
	}

}
<commit_msg>Fix TestAccLoggingBucketConfig_CreateBuckets_withCustomId (#4404)<commit_after>package google

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestAccLoggingBucketConfigFolder_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix": randString(t, 10),
		"folder_name":   "tf-test-" + randString(t, 10),
		"org_id":        getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigFolder_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_folder_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"folder"},
			},
			{
				Config: testAccLoggingBucketConfigFolder_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_folder_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"folder"},
			},
		},
	})
}

func TestAccLoggingBucketConfigProject_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix": randString(t, 10),
		"project_name":  "tf-test-" + randString(t, 10),
		"org_id":        getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigProject_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_project_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"project"},
			},
			{
				Config: testAccLoggingBucketConfigProject_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_project_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"project"},
			},
		},
	})
}

func TestAccLoggingBucketConfigBillingAccount_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix":        randString(t, 10),
		"billing_account_name": "billingAccounts/" + getTestBillingAccountFromEnv(t),
		"org_id":               getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigBillingAccount_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_billing_account_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"billing_account"},
			},
			{
				Config: testAccLoggingBucketConfigBillingAccount_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_billing_account_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"billing_account"},
			},
		},
	})
}

func TestAccLoggingBucketConfigOrganization_basic(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix": randString(t, 10),
		"org_id":        getTestOrgFromEnv(t),
	}

	vcrTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccLoggingBucketConfigOrganization_basic(context, 30),
			},
			{
				ResourceName:            "google_logging_organization_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"organization"},
			},
			{
				Config: testAccLoggingBucketConfigOrganization_basic(context, 40),
			},
			{
				ResourceName:            "google_logging_organization_bucket_config.basic",
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{"organization"},
			},
		},
	})
}

func testAccLoggingBucketConfigFolder_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`
resource "google_folder" "default" {
	display_name = "%{folder_name}"
	parent       = "organizations/%{org_id}"
}

resource "google_logging_folder_bucket_config" "basic" {
	folder    = google_folder.default.name
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func testAccLoggingBucketConfigProject_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`
resource "google_project" "default" {
	project_id = "%{project_name}"
	name       = "%{project_name}"
	org_id     = "%{org_id}"
}

resource "google_logging_project_bucket_config" "basic" {
	project    = google_project.default.name
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func TestAccLoggingBucketConfig_CreateBuckets_withCustomId(t *testing.T) {
	t.Parallel()

	context := map[string]interface{}{
		"random_suffix":        randString(t, 10),
		"billing_account_name": getTestBillingAccountFromEnv(t),
		"org_id":               getTestOrgFromEnv(t),
		"project_name":         "tf-test-" + randString(t, 10),
		"bucket_id":            "tf-test-bucket-" + randString(t, 10),
	}

	configList := getLoggingBucketConfigs(context)

	for res, config := range configList {
		vcrTest(t, resource.TestCase{
			PreCheck:  func() { testAccPreCheck(t) },
			Providers: testAccProviders,
			Steps: []resource.TestStep{
				{
					Config: config,
				},
				{
					ResourceName:            fmt.Sprintf("google_logging_%s_bucket_config.basic", res),
					ImportState:             true,
					ImportStateVerify:       true,
					ImportStateVerifyIgnore: []string{res},
				},
			},
		})
	}
}

func testAccLoggingBucketConfigBillingAccount_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`

data "google_billing_account" "default" {
	billing_account = "%{billing_account_name}"
}

resource "google_logging_billing_account_bucket_config" "basic" {
	billing_account    = data.google_billing_account.default.billing_account
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func testAccLoggingBucketConfigOrganization_basic(context map[string]interface{}, retention int) string {
	return fmt.Sprintf(Nprintf(`
data "google_organization" "default" {
	organization = "%{org_id}"
}

resource "google_logging_organization_bucket_config" "basic" {
	organization    = data.google_organization.default.organization
	location  = "global"
	retention_days = %d
	description = "retention test %d days"
	bucket_id = "_Default"
}
`, context), retention, retention)
}

func getLoggingBucketConfigs(context map[string]interface{}) map[string]string {

	return map[string]string{
		"project": Nprintf(`resource "google_project" "default" {
				project_id = "%{project_name}"
				name       = "%{project_name}"
				org_id     = "%{org_id}"
				billing_account = "%{billing_account_name}"
			}

			resource "google_logging_project_bucket_config" "basic" {
				project    = google_project.default.name
				location  = "global"
				retention_days = 10
				description = "retention test 10 days"
				bucket_id = "%{bucket_id}"
			}`, context),
	}

}
<|endoftext|>


<commit_before>package e2e

import (
	"context"

	"github.com/blang/semver/v4"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/stretchr/testify/require"

	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	meta "k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
	operatorsv2 "github.com/operator-framework/api/pkg/operators/v2"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
)

var _ = Describe("Operator Condition", func() {
	AfterEach(func() {
		TearDown(testNamespace)
	})

	It("OperatorCondition Upgradeable type and overrides", func() {
		By("This test proves that an operator can upgrade successfully when" +
			" Upgrade condition type is set in OperatorCondition spec. Plus, an operator" +
			" chooses not to use OperatorCondition, the upgrade process will proceed as" +
			" expected. The overrides spec in OperatorCondition can be used to override" +
			" the conditions spec. The overrides spec will remain in place until" +
			" they are unset.")
		c := newKubeClient()
		crc := newCRClient()

		// Create a catalog for csvA, csvB, and csvD
		pkgA := genName("a-")
		pkgB := genName("b-")
		pkgD := genName("d-")
		pkgAStable := pkgA + "-stable"
		pkgBStable := pkgB + "-stable"
		pkgDStable := pkgD + "-stable"
		stableChannel := "stable"
		strategyA := newNginxInstallStrategy(pkgAStable, nil, nil)
		strategyB := newNginxInstallStrategy(pkgBStable, nil, nil)
		strategyD := newNginxInstallStrategy(pkgDStable, nil, nil)
		crd := newCRD(genName(pkgA))
		csvA := newCSV(pkgAStable, testNamespace, "", semver.MustParse("0.1.0"), []apiextensions.CustomResourceDefinition{crd}, nil, &strategyA)
		csvB := newCSV(pkgBStable, testNamespace, pkgAStable, semver.MustParse("0.2.0"), []apiextensions.CustomResourceDefinition{crd}, nil, &strategyB)
		csvD := newCSV(pkgDStable, testNamespace, pkgBStable, semver.MustParse("0.3.0"), []apiextensions.CustomResourceDefinition{crd}, nil, &strategyD)

		// Create the initial catalogsources
		manifests := []registry.PackageManifest{
			{
				PackageName: pkgA,
				Channels: []registry.PackageChannel{
					{Name: stableChannel, CurrentCSVName: pkgAStable},
				},
				DefaultChannelName: stableChannel,
			},
		}

		catalog := genName("catalog-")
		_, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalog, testNamespace, manifests, []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA})
		defer cleanupCatalogSource()
		_, err := fetchCatalogSourceOnStatus(crc, catalog, testNamespace, catalogSourceRegistryPodSynced)
		subName := genName("sub-")
		cleanupSub := createSubscriptionForCatalog(crc, testNamespace, subName, catalog, pkgA, stableChannel, pkgAStable, operatorsv1alpha1.ApprovalAutomatic)
		defer cleanupSub()

		// Await csvA's success
		_, err = awaitCSV(crc, testNamespace, csvA.GetName(), csvSucceededChecker)
		require.NoError(GinkgoT(), err)

		// Get the OperatorCondition for csvA and report that it is not upgradeable
		var cond *operatorsv2.OperatorCondition
		upgradeableFalseCondition := metav1.Condition{
			Type:               operatorsv2.Upgradeable,
			Status:             metav1.ConditionFalse,
			Reason:             "test",
			Message:            "test",
			LastTransitionTime: metav1.Now(),
		}

		var currentGen int64
		Eventually(func() error {
			cond, err := crc.OperatorsV2().OperatorConditions(testNamespace).Get(context.TODO(), csvA.GetName(), metav1.GetOptions{})
			if err != nil {
				return err
			}
			currentGen = cond.ObjectMeta.GetGeneration()
			upgradeableFalseCondition.ObservedGeneration = currentGen
			meta.SetStatusCondition(&cond.Spec.Conditions, upgradeableFalseCondition)
			_, err = crc.OperatorsV2().OperatorConditions(testNamespace).Update(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())

		// Update the catalogsources
		manifests = []registry.PackageManifest{
			{
				PackageName: pkgA,
				Channels: []registry.PackageChannel{
					{Name: stableChannel, CurrentCSVName: pkgBStable},
				},
				DefaultChannelName: stableChannel,
			},
		}
		updateInternalCatalog(GinkgoT(), c, crc, catalog, testNamespace, []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}, manifests)

		// Attempt to get the catalog source before creating install plan(s)
		_, err = fetchCatalogSourceOnStatus(crc, catalog, testNamespace, catalogSourceRegistryPodSynced)
		require.NoError(GinkgoT(), err)

		// csvB will be in Pending phase due to csvA reports Upgradeable=False condition
		fetchedCSV, err := fetchCSV(crc, csvB.GetName(), testNamespace, buildCSVReasonChecker(operatorsv1alpha1.CSVReasonOperatorConditionNotUpgradeable))
		require.NoError(GinkgoT(), err)
		require.Equal(GinkgoT(), fetchedCSV.Status.Phase, operatorsv1alpha1.CSVPhasePending)

		// Get the OperatorCondition for csvA and report that it is upgradeable, unblocking csvB
		upgradeableTrueCondition := metav1.Condition{
			Type:               operatorsv2.Upgradeable,
			Status:             metav1.ConditionTrue,
			Reason:             "test",
			Message:            "test",
			LastTransitionTime: metav1.Now(),
		}
		Eventually(func() error {
			cond, err = crc.OperatorsV2().OperatorConditions(testNamespace).Get(context.TODO(), csvA.GetName(), metav1.GetOptions{})
			if err != nil || currentGen == cond.ObjectMeta.GetGeneration() {
				return err
			}
			currentGen = cond.ObjectMeta.GetGeneration()
			upgradeableTrueCondition.ObservedGeneration = cond.ObjectMeta.GetGeneration()
			meta.SetStatusCondition(&cond.Spec.Conditions, upgradeableTrueCondition)
			_, err = crc.OperatorsV2().OperatorConditions(testNamespace).Update(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())

		// Await csvB's success
		_, err = awaitCSV(crc, testNamespace, csvB.GetName(), csvSucceededChecker)
		require.NoError(GinkgoT(), err)

		// Get the OperatorCondition for csvB and purposedly change ObservedGeneration
		// to cause mismatch generation situation
		Eventually(func() error {
			cond, err = crc.OperatorsV2().OperatorConditions(testNamespace).Get(context.TODO(), csvB.GetName(), metav1.GetOptions{})
			if err != nil || currentGen == cond.ObjectMeta.GetGeneration() {
				return err
			}
			currentGen = cond.ObjectMeta.GetGeneration()
			upgradeableTrueCondition.ObservedGeneration = currentGen + 1
			meta.SetStatusCondition(&cond.Status.Conditions, upgradeableTrueCondition)
			_, err = crc.OperatorsV2().OperatorConditions(testNamespace).UpdateStatus(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())

		// Update the catalogsources
		manifests = []registry.PackageManifest{
			{
				PackageName: pkgA,
				Channels: []registry.PackageChannel{
					{Name: stableChannel, CurrentCSVName: pkgDStable},
				},
				DefaultChannelName: stableChannel,
			},
		}

		updateInternalCatalog(GinkgoT(), c, crc, catalog, testNamespace, []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvD}, manifests)
		// Attempt to get the catalog source before creating install plan(s)
		_, err = fetchCatalogSourceOnStatus(crc, catalog, testNamespace, catalogSourceRegistryPodSynced)
		require.NoError(GinkgoT(), err)

		// CSVD will be in Pending status due to overrides in csvB's condition
		fetchedCSV, err = fetchCSV(crc, csvD.GetName(), testNamespace, buildCSVReasonChecker(operatorsv1alpha1.CSVReasonOperatorConditionNotUpgradeable))
		require.NoError(GinkgoT(), err)
		require.Equal(GinkgoT(), fetchedCSV.Status.Phase, operatorsv1alpha1.CSVPhasePending)

		// Get the OperatorCondition for csvB and override the upgradeable false condition
		Eventually(func() error {
			cond, err = crc.OperatorsV2().OperatorConditions(testNamespace).Get(context.TODO(), csvB.GetName(), metav1.GetOptions{})
			if err != nil {
				return err
			}
			meta.SetStatusCondition(&cond.Spec.Overrides, upgradeableTrueCondition)
			// Update the condition
			_, err = crc.OperatorsV2().OperatorConditions(testNamespace).Update(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())
		require.NoError(GinkgoT(), err)

		require.NoError(GinkgoT(), err)
		_, err = awaitCSV(crc, testNamespace, csvD.GetName(), csvSucceededChecker)
		require.NoError(GinkgoT(), err)
	})
})
<commit_msg>Move operator condition e2e to one namespace per spec (#2719)<commit_after>package e2e

import (
	"context"

	"github.com/blang/semver/v4"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/stretchr/testify/require"
	corev1 "k8s.io/api/core/v1"

	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
	meta "k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
	operatorsv2 "github.com/operator-framework/api/pkg/operators/v2"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
)

var _ = Describe("Operator Condition", func() {

	var (
		generatedNamespace corev1.Namespace
	)

	BeforeEach(func() {
		generatedNamespace = SetupGeneratedTestNamespace(genName("operator-conditions-e2e-"))
	})

	AfterEach(func() {
		TeardownNamespace(generatedNamespace.GetName())
	})

	It("OperatorCondition Upgradeable type and overrides", func() {
		By("This test proves that an operator can upgrade successfully when" +
			" Upgrade condition type is set in OperatorCondition spec. Plus, an operator" +
			" chooses not to use OperatorCondition, the upgrade process will proceed as" +
			" expected. The overrides spec in OperatorCondition can be used to override" +
			" the conditions spec. The overrides spec will remain in place until" +
			" they are unset.")
		c := newKubeClient()
		crc := newCRClient()

		// Create a catalog for csvA, csvB, and csvD
		pkgA := genName("a-")
		pkgB := genName("b-")
		pkgD := genName("d-")
		pkgAStable := pkgA + "-stable"
		pkgBStable := pkgB + "-stable"
		pkgDStable := pkgD + "-stable"
		stableChannel := "stable"
		strategyA := newNginxInstallStrategy(pkgAStable, nil, nil)
		strategyB := newNginxInstallStrategy(pkgBStable, nil, nil)
		strategyD := newNginxInstallStrategy(pkgDStable, nil, nil)
		crd := newCRD(genName(pkgA))
		csvA := newCSV(pkgAStable, generatedNamespace.GetName(), "", semver.MustParse("0.1.0"), []apiextensions.CustomResourceDefinition{crd}, nil, &strategyA)
		csvB := newCSV(pkgBStable, generatedNamespace.GetName(), pkgAStable, semver.MustParse("0.2.0"), []apiextensions.CustomResourceDefinition{crd}, nil, &strategyB)
		csvD := newCSV(pkgDStable, generatedNamespace.GetName(), pkgBStable, semver.MustParse("0.3.0"), []apiextensions.CustomResourceDefinition{crd}, nil, &strategyD)

		// Create the initial catalogsources
		manifests := []registry.PackageManifest{
			{
				PackageName: pkgA,
				Channels: []registry.PackageChannel{
					{Name: stableChannel, CurrentCSVName: pkgAStable},
				},
				DefaultChannelName: stableChannel,
			},
		}

		catalog := genName("catalog-")
		_, cleanupCatalogSource := createInternalCatalogSource(c, crc, catalog, generatedNamespace.GetName(), manifests, []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA})
		defer cleanupCatalogSource()
		_, err := fetchCatalogSourceOnStatus(crc, catalog, generatedNamespace.GetName(), catalogSourceRegistryPodSynced)
		subName := genName("sub-")
		cleanupSub := createSubscriptionForCatalog(crc, generatedNamespace.GetName(), subName, catalog, pkgA, stableChannel, pkgAStable, operatorsv1alpha1.ApprovalAutomatic)
		defer cleanupSub()

		// Await csvA's success
		_, err = awaitCSV(crc, generatedNamespace.GetName(), csvA.GetName(), csvSucceededChecker)
		require.NoError(GinkgoT(), err)

		// Get the OperatorCondition for csvA and report that it is not upgradeable
		var cond *operatorsv2.OperatorCondition
		upgradeableFalseCondition := metav1.Condition{
			Type:               operatorsv2.Upgradeable,
			Status:             metav1.ConditionFalse,
			Reason:             "test",
			Message:            "test",
			LastTransitionTime: metav1.Now(),
		}

		var currentGen int64
		Eventually(func() error {
			cond, err := crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Get(context.TODO(), csvA.GetName(), metav1.GetOptions{})
			if err != nil {
				return err
			}
			currentGen = cond.ObjectMeta.GetGeneration()
			upgradeableFalseCondition.ObservedGeneration = currentGen
			meta.SetStatusCondition(&cond.Spec.Conditions, upgradeableFalseCondition)
			_, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Update(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())

		// Update the catalogsources
		manifests = []registry.PackageManifest{
			{
				PackageName: pkgA,
				Channels: []registry.PackageChannel{
					{Name: stableChannel, CurrentCSVName: pkgBStable},
				},
				DefaultChannelName: stableChannel,
			},
		}
		updateInternalCatalog(GinkgoT(), c, crc, catalog, generatedNamespace.GetName(), []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB}, manifests)

		// Attempt to get the catalog source before creating install plan(s)
		_, err = fetchCatalogSourceOnStatus(crc, catalog, generatedNamespace.GetName(), catalogSourceRegistryPodSynced)
		require.NoError(GinkgoT(), err)

		// csvB will be in Pending phase due to csvA reports Upgradeable=False condition
		fetchedCSV, err := fetchCSV(crc, csvB.GetName(), generatedNamespace.GetName(), buildCSVReasonChecker(operatorsv1alpha1.CSVReasonOperatorConditionNotUpgradeable))
		require.NoError(GinkgoT(), err)
		require.Equal(GinkgoT(), fetchedCSV.Status.Phase, operatorsv1alpha1.CSVPhasePending)

		// Get the OperatorCondition for csvA and report that it is upgradeable, unblocking csvB
		upgradeableTrueCondition := metav1.Condition{
			Type:               operatorsv2.Upgradeable,
			Status:             metav1.ConditionTrue,
			Reason:             "test",
			Message:            "test",
			LastTransitionTime: metav1.Now(),
		}
		Eventually(func() error {
			cond, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Get(context.TODO(), csvA.GetName(), metav1.GetOptions{})
			if err != nil || currentGen == cond.ObjectMeta.GetGeneration() {
				return err
			}
			currentGen = cond.ObjectMeta.GetGeneration()
			upgradeableTrueCondition.ObservedGeneration = cond.ObjectMeta.GetGeneration()
			meta.SetStatusCondition(&cond.Spec.Conditions, upgradeableTrueCondition)
			_, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Update(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())

		// Await csvB's success
		_, err = awaitCSV(crc, generatedNamespace.GetName(), csvB.GetName(), csvSucceededChecker)
		require.NoError(GinkgoT(), err)

		// Get the OperatorCondition for csvB and purposedly change ObservedGeneration
		// to cause mismatch generation situation
		Eventually(func() error {
			cond, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Get(context.TODO(), csvB.GetName(), metav1.GetOptions{})
			if err != nil || currentGen == cond.ObjectMeta.GetGeneration() {
				return err
			}
			currentGen = cond.ObjectMeta.GetGeneration()
			upgradeableTrueCondition.ObservedGeneration = currentGen + 1
			meta.SetStatusCondition(&cond.Status.Conditions, upgradeableTrueCondition)
			_, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).UpdateStatus(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())

		// Update the catalogsources
		manifests = []registry.PackageManifest{
			{
				PackageName: pkgA,
				Channels: []registry.PackageChannel{
					{Name: stableChannel, CurrentCSVName: pkgDStable},
				},
				DefaultChannelName: stableChannel,
			},
		}

		updateInternalCatalog(GinkgoT(), c, crc, catalog, generatedNamespace.GetName(), []apiextensions.CustomResourceDefinition{crd}, []operatorsv1alpha1.ClusterServiceVersion{csvA, csvB, csvD}, manifests)
		// Attempt to get the catalog source before creating install plan(s)
		_, err = fetchCatalogSourceOnStatus(crc, catalog, generatedNamespace.GetName(), catalogSourceRegistryPodSynced)
		require.NoError(GinkgoT(), err)

		// CSVD will be in Pending status due to overrides in csvB's condition
		fetchedCSV, err = fetchCSV(crc, csvD.GetName(), generatedNamespace.GetName(), buildCSVReasonChecker(operatorsv1alpha1.CSVReasonOperatorConditionNotUpgradeable))
		require.NoError(GinkgoT(), err)
		require.Equal(GinkgoT(), fetchedCSV.Status.Phase, operatorsv1alpha1.CSVPhasePending)

		// Get the OperatorCondition for csvB and override the upgradeable false condition
		Eventually(func() error {
			cond, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Get(context.TODO(), csvB.GetName(), metav1.GetOptions{})
			if err != nil {
				return err
			}
			meta.SetStatusCondition(&cond.Spec.Overrides, upgradeableTrueCondition)
			// Update the condition
			_, err = crc.OperatorsV2().OperatorConditions(generatedNamespace.GetName()).Update(context.TODO(), cond, metav1.UpdateOptions{})
			return err
		}, pollInterval, pollDuration).Should(Succeed())
		require.NoError(GinkgoT(), err)

		require.NoError(GinkgoT(), err)
		_, err = awaitCSV(crc, generatedNamespace.GetName(), csvD.GetName(), csvSucceededChecker)
		require.NoError(GinkgoT(), err)
	})
})
<|endoftext|>


<commit_before>package async

import (
	"testing"

	"code.google.com/p/go.net/context"
	"github.com/jbenet/go-ipfs/blocks"
)

func TestForwardTwo(t *testing.T) {
	const n = 2
	in := make(chan *blocks.Block, n)
	ctx := context.Background()
	out := ForwardN(ctx, in, n)

	in <- blocks.NewBlock([]byte("one"))
	in <- blocks.NewBlock([]byte("two"))

	_ = <-out // 1
	_ = <-out // 2

	_, ok := <-out // closed
	if !ok {
		return
	}
	t.Fail()
}

func TestCloseInput(t *testing.T) {
	const n = 2
	in := make(chan *blocks.Block, 0)
	ctx := context.Background()
	out := ForwardN(ctx, in, n)

	close(in)
	_, ok := <-out // closed
	if !ok {
		return
	}
	t.Fatal("input channel closed, but output channel not")

}

func TestContextClosedWhenBlockingOnInput(t *testing.T) {
	const n = 1 // but we won't ever send a block
	ctx, cancel := context.WithCancel(context.Background())
	out := ForwardN(ctx, make(chan *blocks.Block), n)

	cancel() // before sending anything
	_, ok := <-out
	if !ok {
		return
	}
	t.Fail()
}
<commit_msg>fix(dep)<commit_after>package async

import (
	"testing"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	"github.com/jbenet/go-ipfs/blocks"
)

func TestForwardTwo(t *testing.T) {
	const n = 2
	in := make(chan *blocks.Block, n)
	ctx := context.Background()
	out := ForwardN(ctx, in, n)

	in <- blocks.NewBlock([]byte("one"))
	in <- blocks.NewBlock([]byte("two"))

	_ = <-out // 1
	_ = <-out // 2

	_, ok := <-out // closed
	if !ok {
		return
	}
	t.Fail()
}

func TestCloseInput(t *testing.T) {
	const n = 2
	in := make(chan *blocks.Block, 0)
	ctx := context.Background()
	out := ForwardN(ctx, in, n)

	close(in)
	_, ok := <-out // closed
	if !ok {
		return
	}
	t.Fatal("input channel closed, but output channel not")

}

func TestContextClosedWhenBlockingOnInput(t *testing.T) {
	const n = 1 // but we won't ever send a block
	ctx, cancel := context.WithCancel(context.Background())
	out := ForwardN(ctx, make(chan *blocks.Block), n)

	cancel() // before sending anything
	_, ok := <-out
	if !ok {
		return
	}
	t.Fail()
}
<|endoftext|>


<commit_before>package csaupgrade

import (
	"bytes"
	"fmt"

	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)

const csaAnnotationName = "kubectl.kubernetes.io/last-applied-configuration"

var csaAnnotationFieldSet = fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", csaAnnotationName))

// Upgrades the Manager information for fields managed with client-side-apply (CSA)
// Prepares fields owned by `csaManager` for 'Update' operations for use now
// with the given `ssaManager` for `Apply` operations.
//
// This transformation should be performed on an object if it has been previously
// managed using client-side-apply to prepare it for future use with
// server-side-apply.
//
// Caveats:
//  1. This operation is not reversible. Information about which fields the client
//     owned will be lost in this operation.
//  2. Supports being performed either before or after initial server-side apply.
//  3. Client-side apply tends to own more fields (including fields that are defaulted),
//     this will possibly remove this defaults, they will be re-defaulted, that's fine.
//  4. Care must be taken to not overwrite the managed fields on the server if they
//     have changed before sending a patch.
//
// obj - Target of the operation which has been managed with CSA in the past
// csaManagerName - Name of FieldManager formerly used for `Update` operations
// ssaManagerName - Name of FieldManager formerly used for `Apply` operations
// Returns: a copy of the `obj` paramter with its managed fields modified so that
// it may be used with server-side apply in the future.
func UpgradeManagedFields(
	obj runtime.Object,
	csaManagerName string,
	ssaManagerName string,
) (runtime.Object, error) {
	accessor, err := meta.Accessor(obj)
	if err != nil {
		return nil, fmt.Errorf("error accessing object metadata: %w", err)
	}

	// Create managed fields clone since we modify the values
	var managedFields []metav1.ManagedFieldsEntry
	managedFields = append(managedFields, accessor.GetManagedFields()...)

	// Locate SSA manager
	ssaManagerIndex, ssaManagerExists := findFirstIndex(managedFields,
		func(entry metav1.ManagedFieldsEntry) bool {
			return entry.Manager == ssaManagerName &&
				entry.Operation == metav1.ManagedFieldsOperationApply &&
				entry.Subresource == ""
		})

	if ssaManagerExists {
		err = unionManagerIntoIndex(managedFields, ssaManagerIndex, csaManagerName)
		if err != nil {
			return nil, err
		}

	} else {
		// SSA manager does not exist. Find the most recent matching CSA manager,
		// convert it to an SSA manager.
		//
		// (find first index, since managed fields are sorted so that most recent is
		//  first in the list)
		csaManagerIndex, csaManagerExists := findFirstIndex(managedFields,
			func(entry metav1.ManagedFieldsEntry) bool {
				return entry.Manager == csaManagerName &&
					entry.Operation == metav1.ManagedFieldsOperationUpdate &&
					entry.Subresource == ""
			})

		if !csaManagerExists {
			// There are no CSA managers that need to be converted. Nothing to do
			// Return early
			return obj, nil
		}

		// Convert CSA manager into SSA manager
		managedFields[csaManagerIndex].Operation = metav1.ManagedFieldsOperationApply
		managedFields[csaManagerIndex].Manager = ssaManagerName

		err = unionManagerIntoIndex(managedFields, csaManagerIndex, csaManagerName)
		if err != nil {
			return nil, err
		}
	}

	// Create version of managed fields which has no CSA managers with the given name
	filteredManagers := filter(managedFields, func(entry metav1.ManagedFieldsEntry) bool {
		return !(entry.Manager == csaManagerName &&
			entry.Operation == metav1.ManagedFieldsOperationUpdate &&
			entry.Subresource == "")
	})

	copied := obj.DeepCopyObject()
	copiedAccessor, err := meta.Accessor(copied)
	if err != nil {
		return nil, fmt.Errorf("failed to get meta accessor for copied object: %w", err)
	}
	copiedAccessor.SetManagedFields(filteredManagers)

	// Wipe out CSA annotation if it exists
	annotations := copiedAccessor.GetAnnotations()
	delete(annotations, csaAnnotationName)
	copiedAccessor.SetAnnotations(annotations)

	return copied, nil
}

// Locates an Update manager entry named `csaManagerName` with the same APIVersion
// as the manager at the targetIndex. Unions both manager's fields together
// into the manager specified by `targetIndex`. No other managers are modified.
func unionManagerIntoIndex(entries []metav1.ManagedFieldsEntry, targetIndex int, csaManagerName string) error {
	ssaManager := entries[targetIndex]

	// find Update manager of same APIVersion, union ssa fields with it.
	// discard all other Update managers of the same name
	csaManagerIndex, csaManagerExists := findFirstIndex(entries,
		func(entry metav1.ManagedFieldsEntry) bool {
			return entry.Manager == csaManagerName &&
				entry.Operation == metav1.ManagedFieldsOperationUpdate &&
				entry.Subresource == "" &&
				entry.APIVersion == ssaManager.APIVersion
		})

	targetFieldSet, err := fieldsToSet(*ssaManager.FieldsV1)
	if err != nil {
		return fmt.Errorf("failed to convert fields to set: %w", err)
	}

	combinedFieldSet := &targetFieldSet

	// Union the csa manager with the existing SSA manager. Do nothing if
	// there was no good candidate found
	if csaManagerExists {
		csaManager := entries[csaManagerIndex]

		csaFieldSet, err := fieldsToSet(*csaManager.FieldsV1)
		if err != nil {
			return fmt.Errorf("failed to convert fields to set: %w", err)
		}

		combinedFieldSet = combinedFieldSet.Union(&csaFieldSet)
	}

	// Ensure that the resultant fieldset does not include the
	// last applied annotation
	combinedFieldSet = combinedFieldSet.Difference(csaAnnotationFieldSet)

	// Encode the fields back to the serialized format
	combinedFieldSetEncoded, err := setToFields(*combinedFieldSet)
	if err != nil {
		return fmt.Errorf("failed to encode field set: %w", err)
	}

	entries[targetIndex].FieldsV1 = &combinedFieldSetEncoded
	return nil
}

func findFirstIndex[T any](
	collection []T,
	predicate func(T) bool,
) (int, bool) {
	for idx, entry := range collection {
		if predicate(entry) {
			return idx, true
		}
	}

	return -1, false
}

func filter[T any](
	collection []T,
	predicate func(T) bool,
) []T {
	result := make([]T, 0, len(collection))

	for _, value := range collection {
		if predicate(value) {
			result = append(result, value)
		}
	}

	if len(result) == 0 {
		return nil
	}

	return result
}

// Included from fieldmanager.internal to avoid dependency cycle
// FieldsToSet creates a set paths from an input trie of fields
func fieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) {
	err = s.FromJSON(bytes.NewReader(f.Raw))
	return s, err
}

// SetToFields creates a trie of fields from an input set of paths
func setToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) {
	f.Raw, err = s.ToJSON()
	return f, err
}
<commit_msg>Update staging/src/k8s.io/client-go/util/csaupgrade/upgrade.go<commit_after>package csaupgrade

import (
	"bytes"
	"fmt"

	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)

const csaAnnotationName = "kubectl.kubernetes.io/last-applied-configuration"

var csaAnnotationFieldSet = fieldpath.NewSet(fieldpath.MakePathOrDie("metadata", "annotations", csaAnnotationName))

// Upgrades the Manager information for fields managed with client-side-apply (CSA)
// Prepares fields owned by `csaManager` for 'Update' operations for use now
// with the given `ssaManager` for `Apply` operations.
//
// This transformation should be performed on an object if it has been previously
// managed using client-side-apply to prepare it for future use with
// server-side-apply.
//
// Caveats:
//  1. This operation is not reversible. Information about which fields the client
//     owned will be lost in this operation.
//  2. Supports being performed either before or after initial server-side apply.
//  3. Client-side apply tends to own more fields (including fields that are defaulted),
//     this will possibly remove this defaults, they will be re-defaulted, that's fine.
//  4. Care must be taken to not overwrite the managed fields on the server if they
//     have changed before sending a patch.
//
// obj - Target of the operation which has been managed with CSA in the past
// csaManagerName - Name of FieldManager formerly used for `Update` operations
// ssaManagerName - Name of FieldManager formerly used for `Apply` operations
// Returns: a copy of the `obj` paramter with its managed fields modified so that
// it may be used with server-side apply in the future.
func UpgradeManagedFields(
	obj runtime.Object,
	csaManagerName string,
	ssaManagerName string,
) (runtime.Object, error) {
	accessor, err := meta.Accessor(obj)
	if err != nil {
		return nil, fmt.Errorf("error accessing object metadata: %w", err)
	}

	// Create managed fields clone since we modify the values
	var managedFields []metav1.ManagedFieldsEntry
	managedFields = append(managedFields, accessor.GetManagedFields()...)

	// Locate SSA manager
	replaceIndex, managerExists := findFirstIndex(managedFields,
		func(entry metav1.ManagedFieldsEntry) bool {
			return entry.Manager == ssaManagerName &&
				entry.Operation == metav1.ManagedFieldsOperationApply &&
				entry.Subresource == ""
		})

	if !managerExists {
		// SSA manager does not exist. Find the most recent matching CSA manager,
		// convert it to an SSA manager.
		//
		// (find first index, since managed fields are sorted so that most recent is
		//  first in the list)
		replaceIndex, managerExists = findFirstIndex(managedFields,
			func(entry metav1.ManagedFieldsEntry) bool {
				return entry.Manager == csaManagerName &&
					entry.Operation == metav1.ManagedFieldsOperationUpdate &&
					entry.Subresource == ""
			})

		if !managerExists {
			// There are no CSA managers that need to be converted. Nothing to do
			// Return early
			return obj, nil
		}

		// Convert CSA manager into SSA manager
		managedFields[replaceIndex].Operation = metav1.ManagedFieldsOperationApply
		managedFields[replaceIndex].Manager = ssaManagerName
	}
	err = unionManagerIntoIndex(managedFields, replaceIndex, csaManagerName)
	if err != nil {
		return nil, err
	}

	// Create version of managed fields which has no CSA managers with the given name
	filteredManagers := filter(managedFields, func(entry metav1.ManagedFieldsEntry) bool {
		return !(entry.Manager == csaManagerName &&
			entry.Operation == metav1.ManagedFieldsOperationUpdate &&
			entry.Subresource == "")
	})

	copied := obj.DeepCopyObject()
	copiedAccessor, err := meta.Accessor(copied)
	if err != nil {
		return nil, fmt.Errorf("failed to get meta accessor for copied object: %w", err)
	}
	copiedAccessor.SetManagedFields(filteredManagers)

	// Wipe out CSA annotation if it exists
	annotations := copiedAccessor.GetAnnotations()
	delete(annotations, csaAnnotationName)
	copiedAccessor.SetAnnotations(annotations)

	return copied, nil
}

// Locates an Update manager entry named `csaManagerName` with the same APIVersion
// as the manager at the targetIndex. Unions both manager's fields together
// into the manager specified by `targetIndex`. No other managers are modified.
func unionManagerIntoIndex(entries []metav1.ManagedFieldsEntry, targetIndex int, csaManagerName string) error {
	ssaManager := entries[targetIndex]

	// find Update manager of same APIVersion, union ssa fields with it.
	// discard all other Update managers of the same name
	csaManagerIndex, csaManagerExists := findFirstIndex(entries,
		func(entry metav1.ManagedFieldsEntry) bool {
			return entry.Manager == csaManagerName &&
				entry.Operation == metav1.ManagedFieldsOperationUpdate &&
				entry.Subresource == "" &&
				entry.APIVersion == ssaManager.APIVersion
		})

	targetFieldSet, err := fieldsToSet(*ssaManager.FieldsV1)
	if err != nil {
		return fmt.Errorf("failed to convert fields to set: %w", err)
	}

	combinedFieldSet := &targetFieldSet

	// Union the csa manager with the existing SSA manager. Do nothing if
	// there was no good candidate found
	if csaManagerExists {
		csaManager := entries[csaManagerIndex]

		csaFieldSet, err := fieldsToSet(*csaManager.FieldsV1)
		if err != nil {
			return fmt.Errorf("failed to convert fields to set: %w", err)
		}

		combinedFieldSet = combinedFieldSet.Union(&csaFieldSet)
	}

	// Ensure that the resultant fieldset does not include the
	// last applied annotation
	combinedFieldSet = combinedFieldSet.Difference(csaAnnotationFieldSet)

	// Encode the fields back to the serialized format
	combinedFieldSetEncoded, err := setToFields(*combinedFieldSet)
	if err != nil {
		return fmt.Errorf("failed to encode field set: %w", err)
	}

	entries[targetIndex].FieldsV1 = &combinedFieldSetEncoded
	return nil
}

func findFirstIndex[T any](
	collection []T,
	predicate func(T) bool,
) (int, bool) {
	for idx, entry := range collection {
		if predicate(entry) {
			return idx, true
		}
	}

	return -1, false
}

func filter[T any](
	collection []T,
	predicate func(T) bool,
) []T {
	result := make([]T, 0, len(collection))

	for _, value := range collection {
		if predicate(value) {
			result = append(result, value)
		}
	}

	if len(result) == 0 {
		return nil
	}

	return result
}

// Included from fieldmanager.internal to avoid dependency cycle
// FieldsToSet creates a set paths from an input trie of fields
func fieldsToSet(f metav1.FieldsV1) (s fieldpath.Set, err error) {
	err = s.FromJSON(bytes.NewReader(f.Raw))
	return s, err
}

// SetToFields creates a trie of fields from an input set of paths
func setToFields(s fieldpath.Set) (f metav1.FieldsV1, err error) {
	f.Raw, err = s.ToJSON()
	return f, err
}
<|endoftext|>