<commit_before>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package tlf

import (
	"errors"
	"reflect"
	"sort"

	"github.com/keybase/client/go/protocol/keybase1"
)

// Handle uniquely identified top-level folders by readers and writers.
//
// NOTE: if you change this type, ensure you update the `NumField` check in
// `MakeHandle`, and that the new fields are correctly checked for equality in
// `DeepEqual`.
// TODO: Have separate types for writers vs. readers.
type Handle struct {
	Writers           []keybase1.UserOrTeamID    `codec:"w,omitempty"`
	Readers           []keybase1.UserOrTeamID    `codec:"r,omitempty"`
	UnresolvedWriters []keybase1.SocialAssertion `codec:"uw,omitempty"`
	UnresolvedReaders []keybase1.SocialAssertion `codec:"ur,omitempty"`
	ConflictInfo      *HandleExtension           `codec:"ci,omitempty"`
	FinalizedInfo     *HandleExtension           `codec:"fi,omitempty"`

	// caching field to track whether we've sorted the slices.
	sorted bool `codec:"-"`
}

// errNoWriters is the error returned by MakeHandle if it is
// passed an empty list of writers.
var errNoWriters = errors.New("Cannot make TLF handle with no writers; need rekey?")

// errInvalidWriter is the error returned by MakeHandle if it
// is passed an invalid writer.
var errInvalidWriter = errors.New("Cannot make TLF handle with invalid writer")

// errInvalidReader is the error returned by MakeHandle if it
// is passed an invalid reader.
var errInvalidReader = errors.New("Cannot make TLF handle with invalid reader")

// UIDList can be used to lexicographically sort UserOrTeamIDs.
type UIDList []keybase1.UserOrTeamID

func (u UIDList) Len() int {
	return len(u)
}

func (u UIDList) Less(i, j int) bool {
	return u[i].Less(u[j])
}

func (u UIDList) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// SocialAssertionList can be used to lexicographically sort SocialAssertions.
type SocialAssertionList []keybase1.SocialAssertion

func (u SocialAssertionList) Len() int {
	return len(u)
}

func (u SocialAssertionList) Less(i, j int) bool {
	si := u[i].String()
	sj := u[j].String()
	return si < sj
}

func (u SocialAssertionList) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// MakeHandle creates a Handle from the given list of
// readers and writers. If the given reader list contains just
// keybase1.PUBLIC_UID, then the returned handle will be for a public
// folder. Otherwise, it will be private. PUBLIC_UID shouldn't be in
// any list in any other case.
func MakeHandle(
	writers, readers []keybase1.UserOrTeamID,
	unresolvedWriters, unresolvedReaders []keybase1.SocialAssertion,
	extensions []HandleExtension) (Handle, error) {
	if reflect.ValueOf(Handle{}).NumField() != 7 {
		panic(errors.New(
			"Unexpected number of fields in Handle; please update the check " +
				"above and ensure that Handle.DeepEqual() accounts for the " +
				"new field"))
	}
	if len(writers) == 0 {
		return Handle{}, errNoWriters
	}

	if writers[0].IsTeamOrSubteam() {
		// Right now we only support single-team private TLFs.
		if len(writers) > 1 || len(unresolvedWriters) != 0 {
			return Handle{}, errInvalidWriter
		} else if len(readers) != 0 || len(unresolvedReaders) != 0 {
			return Handle{}, errInvalidReader
		}
	}

	for i, w := range writers {
		if w == keybase1.PUBLIC_UID {
			return Handle{}, errInvalidWriter
		} else if i > 0 && w.IsTeamOrSubteam() {
			return Handle{}, errInvalidWriter
		}
	}

	// If we have more than one reader, none of them should be the
	// public UID.  And no readers should be a team.
	checkPublic := (len(readers) + len(unresolvedReaders)) > 1
	for _, r := range readers {
		if checkPublic && r == keybase1.PUBLIC_UID {
			return Handle{}, errInvalidReader
		} else if r.IsTeamOrSubteam() {
			return Handle{}, errInvalidReader
		}
	}

	// TODO: Check for overlap between readers and writers, and
	// for duplicates.

	writersCopy := make([]keybase1.UserOrTeamID, len(writers))
	copy(writersCopy, writers)
	sort.Sort(UIDList(writersCopy))

	var readersCopy []keybase1.UserOrTeamID
	if len(readers) > 0 {
		readersCopy = make([]keybase1.UserOrTeamID, len(readers))
		copy(readersCopy, readers)
		sort.Sort(UIDList(readersCopy))
	}

	var unresolvedWritersCopy []keybase1.SocialAssertion
	if len(unresolvedWriters) > 0 {
		unresolvedWritersCopy = make([]keybase1.SocialAssertion, len(unresolvedWriters))
		copy(unresolvedWritersCopy, unresolvedWriters)
		sort.Sort(SocialAssertionList(unresolvedWritersCopy))
	}

	var unresolvedReadersCopy []keybase1.SocialAssertion
	if len(unresolvedReaders) > 0 {
		unresolvedReadersCopy = make([]keybase1.SocialAssertion, len(unresolvedReaders))
		copy(unresolvedReadersCopy, unresolvedReaders)
		sort.Sort(SocialAssertionList(unresolvedReadersCopy))
	}

	conflictInfo, finalizedInfo := HandleExtensionList(extensions).Splat()

	return Handle{
		Writers:           writersCopy,
		Readers:           readersCopy,
		UnresolvedWriters: unresolvedWritersCopy,
		UnresolvedReaders: unresolvedReadersCopy,
		ConflictInfo:      conflictInfo,
		FinalizedInfo:     finalizedInfo,
	}, nil
}

// IsBackedByTeam returns true if h represents a TLF backed by a team. It could
// be either a SingleTeam TLF or a private/public TLF backed by an implicit
// team.
func (h Handle) IsBackedByTeam() bool {
	if len(h.Writers) != 1 ||
		len(h.Readers) != 0 ||
		len(h.UnresolvedReaders) != 0 ||
		len(h.UnresolvedWriters) != 0 {
		return false
	}
	return h.Writers[0].IsTeamOrSubteam()
}

// Type returns the type of TLF this Handle represents.
func (h Handle) Type() Type {
	if len(h.Readers) == 1 &&
		h.Readers[0].Equal(keybase1.PublicUID.AsUserOrTeam()) {
		return Public
	} else if len(h.Writers) == 1 && h.Writers[0].IsTeamOrSubteam() {
		return SingleTeam
	}
	return Private
}

// TypeForKeying returns the keying type for the handle h.
func (h Handle) TypeForKeying() KeyingType {
	if h.IsBackedByTeam() {
		return TeamKeying
	}
	return h.Type().ToKeyingType()
}

func (h Handle) findUserInList(user keybase1.UserOrTeamID,
	users []keybase1.UserOrTeamID) bool {
	for _, u := range users {
		if u == user {
			return true
		}
	}
	return false
}

// IsWriter returns whether or not the given user is a writer for the
// top-level folder represented by this Handle.
func (h Handle) IsWriter(user keybase1.UserOrTeamID) bool {
	if h.TypeForKeying() == TeamKeying {
		panic("Can't call Handle.IsWriter() for a single team TLF")
	}
	return h.findUserInList(user, h.Writers)
}

// IsReader returns whether or not the given user is a reader for the
// top-level folder represented by this Handle.
func (h Handle) IsReader(user keybase1.UserOrTeamID) bool {
	if h.TypeForKeying() == TeamKeying {
		panic("Can't call Handle.IsReader() for a single team TLF")
	}
	return h.TypeForKeying() == PublicKeying ||
		h.findUserInList(user, h.Readers) ||
		h.IsWriter(user)
}

// ResolvedUsers returns the concatenation of h.Writers and h.Readers,
// except if the handle is public, the returned list won't contain
// PUBLIC_UID.
func (h Handle) ResolvedUsers() []keybase1.UserOrTeamID {
	var resolvedUsers []keybase1.UserOrTeamID
	resolvedUsers = append(resolvedUsers, h.Writers...)
	if h.TypeForKeying() == PrivateKeying {
		resolvedUsers = append(resolvedUsers, h.Readers...)
	}
	return resolvedUsers
}

// HasUnresolvedUsers returns true if this handle has any unresolved
// writers or readers.
func (h Handle) HasUnresolvedUsers() bool {
	return len(h.UnresolvedWriters) > 0 || len(h.UnresolvedReaders) > 0
}

// UnresolvedUsers returns the concatenation of h.UnresolvedWriters
// and h.UnresolvedReaders.
func (h Handle) UnresolvedUsers() []keybase1.SocialAssertion {
	var unresolvedUsers []keybase1.SocialAssertion
	unresolvedUsers = append(unresolvedUsers, h.UnresolvedWriters...)
	unresolvedUsers = append(unresolvedUsers, h.UnresolvedReaders...)
	return unresolvedUsers
}

func uidSliceToSet(s []keybase1.UserOrTeamID) map[keybase1.UserOrTeamID]bool {
	m := make(map[keybase1.UserOrTeamID]bool, len(s))
	for _, u := range s {
		m[u] = true
	}
	return m
}

func assertionSliceToSet(s []keybase1.SocialAssertion) map[keybase1.SocialAssertion]bool {
	m := make(map[keybase1.SocialAssertion]bool, len(s))
	for _, u := range s {
		m[u] = true
	}
	return m
}

func resolveAssertions(
	assertions map[keybase1.SocialAssertion]keybase1.UID,
	unresolved []keybase1.SocialAssertion, resolved []keybase1.UserOrTeamID) (
	map[keybase1.UserOrTeamID]bool, []keybase1.SocialAssertion) {
	resolvedMap := uidSliceToSet(resolved)
	unresolvedMap := assertionSliceToSet(unresolved)
	for a, u := range assertions {
		if unresolvedMap[a] {
			resolvedMap[u.AsUserOrTeam()] = true
			delete(unresolvedMap, a)
		}
	}
	return resolvedMap, assertionSetToSlice(unresolvedMap)
}

func uidSetToSlice(m map[keybase1.UserOrTeamID]bool) (
	s []keybase1.UserOrTeamID) {
	for u := range m {
		s = append(s, u)
	}
	return s
}

func assertionSetToSlice(m map[keybase1.SocialAssertion]bool) (s []keybase1.SocialAssertion) {
	for u := range m {
		s = append(s, u)
	}
	return s
}

// ResolveAssertions creates a new Handle given an existing one with
// while resolving the passed assertions.
func (h Handle) ResolveAssertions(
	assertions map[keybase1.SocialAssertion]keybase1.UID) Handle {
	if len(assertions) == 0 || (len(h.UnresolvedWriters) == 0 && len(h.UnresolvedReaders) == 0) || h.IsFinal() {
		return h
	}
	var resolvedWriters, resolvedReaders map[keybase1.UserOrTeamID]bool
	resolvedWriters, h.UnresolvedWriters = resolveAssertions(assertions, h.UnresolvedWriters, h.Writers)
	resolvedReaders, h.UnresolvedReaders = resolveAssertions(assertions, h.UnresolvedReaders, h.Readers)
	h.Writers = uidSetToSlice(resolvedWriters)
	for _, u := range h.Writers {
		delete(resolvedReaders, u)
	}
	h.Readers = uidSetToSlice(resolvedReaders)
	sort.Sort(UIDList(h.Writers))
	sort.Sort(UIDList(h.Readers))
	sort.Sort(SocialAssertionList(h.UnresolvedWriters))
	sort.Sort(SocialAssertionList(h.UnresolvedReaders))
	return h
}

// Extensions returns a list of extensions for the given handle.
func (h Handle) Extensions() (extensions []HandleExtension) {
	if h.ConflictInfo != nil {
		extensions = append(extensions, *h.ConflictInfo)
	}
	if h.FinalizedInfo != nil {
		extensions = append(extensions, *h.FinalizedInfo)
	}
	return extensions
}

// IsFinal returns true if the handle has been finalized.
func (h Handle) IsFinal() bool {
	return h.FinalizedInfo != nil
}

// IsConflict returns true if the handle is a conflict handle.
func (h Handle) IsConflict() bool {
	return h.ConflictInfo != nil
}

// DeepEqual returns true if the handle is equal to another handle.
func (h Handle) DeepEqual(other Handle) bool {
	if !h.sorted {
		sort.Sort(UIDList(h.Writers))
		sort.Sort(UIDList(h.Readers))
		sort.Sort(SocialAssertionList(h.UnresolvedWriters))
		sort.Sort(SocialAssertionList(h.UnresolvedReaders))
		h.sorted = true
	}
	if !other.sorted {
		sort.Sort(UIDList(other.Writers))
		sort.Sort(UIDList(other.Readers))
		sort.Sort(SocialAssertionList(other.UnresolvedWriters))
		sort.Sort(SocialAssertionList(other.UnresolvedReaders))
		other.sorted = true
	}

	if len(h.Writers) != len(other.Writers) {
		return false
	}
	if len(h.UnresolvedWriters) != len(other.UnresolvedWriters) {
		return false
	}
	if len(h.Readers) != len(other.Readers) {
		return false
	}
	if len(h.UnresolvedReaders) != len(other.UnresolvedReaders) {
		return false
	}
	for i, v := range h.Writers {
		if other.Writers[i] != v {
			return false
		}
	}
	for i, v := range h.UnresolvedWriters {
		if other.UnresolvedWriters[i] != v {
			return false
		}
	}
	for i, v := range h.Readers {
		if other.Readers[i] != v {
			return false
		}
	}
	for i, v := range h.UnresolvedReaders {
		if other.UnresolvedReaders[i] != v {
			return false
		}
	}
	if h.IsConflict() != other.IsConflict() {
		return false
	}
	if h.IsFinal() != other.IsFinal() {
		return false
	}
	if h.ConflictInfo != nil &&
		h.ConflictInfo.String() != other.ConflictInfo.String() {
		return false
	}
	if h.FinalizedInfo != nil &&
		h.FinalizedInfo.String() != other.FinalizedInfo.String() {
		return false
	}

	return true
}
<commit_msg>tlf.handle: Do the length check before sorting.<commit_after>// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package tlf

import (
	"errors"
	"reflect"
	"sort"

	"github.com/keybase/client/go/protocol/keybase1"
)

// Handle uniquely identified top-level folders by readers and writers.
//
// NOTE: if you change this type, ensure you update the `NumField` check in
// `MakeHandle`, and that the new fields are correctly checked for equality in
// `DeepEqual`.
// TODO: Have separate types for writers vs. readers.
type Handle struct {
	Writers           []keybase1.UserOrTeamID    `codec:"w,omitempty"`
	Readers           []keybase1.UserOrTeamID    `codec:"r,omitempty"`
	UnresolvedWriters []keybase1.SocialAssertion `codec:"uw,omitempty"`
	UnresolvedReaders []keybase1.SocialAssertion `codec:"ur,omitempty"`
	ConflictInfo      *HandleExtension           `codec:"ci,omitempty"`
	FinalizedInfo     *HandleExtension           `codec:"fi,omitempty"`

	// caching field to track whether we've sorted the slices.
	sorted bool `codec:"-"`
}

// errNoWriters is the error returned by MakeHandle if it is
// passed an empty list of writers.
var errNoWriters = errors.New("Cannot make TLF handle with no writers; need rekey?")

// errInvalidWriter is the error returned by MakeHandle if it
// is passed an invalid writer.
var errInvalidWriter = errors.New("Cannot make TLF handle with invalid writer")

// errInvalidReader is the error returned by MakeHandle if it
// is passed an invalid reader.
var errInvalidReader = errors.New("Cannot make TLF handle with invalid reader")

// UIDList can be used to lexicographically sort UserOrTeamIDs.
type UIDList []keybase1.UserOrTeamID

func (u UIDList) Len() int {
	return len(u)
}

func (u UIDList) Less(i, j int) bool {
	return u[i].Less(u[j])
}

func (u UIDList) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// SocialAssertionList can be used to lexicographically sort SocialAssertions.
type SocialAssertionList []keybase1.SocialAssertion

func (u SocialAssertionList) Len() int {
	return len(u)
}

func (u SocialAssertionList) Less(i, j int) bool {
	si := u[i].String()
	sj := u[j].String()
	return si < sj
}

func (u SocialAssertionList) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// MakeHandle creates a Handle from the given list of
// readers and writers. If the given reader list contains just
// keybase1.PUBLIC_UID, then the returned handle will be for a public
// folder. Otherwise, it will be private. PUBLIC_UID shouldn't be in
// any list in any other case.
func MakeHandle(
	writers, readers []keybase1.UserOrTeamID,
	unresolvedWriters, unresolvedReaders []keybase1.SocialAssertion,
	extensions []HandleExtension) (Handle, error) {
	if reflect.ValueOf(Handle{}).NumField() != 7 {
		panic(errors.New(
			"Unexpected number of fields in Handle; please update the check " +
				"above and ensure that Handle.DeepEqual() accounts for the " +
				"new field"))
	}
	if len(writers) == 0 {
		return Handle{}, errNoWriters
	}

	if writers[0].IsTeamOrSubteam() {
		// Right now we only support single-team private TLFs.
		if len(writers) > 1 || len(unresolvedWriters) != 0 {
			return Handle{}, errInvalidWriter
		} else if len(readers) != 0 || len(unresolvedReaders) != 0 {
			return Handle{}, errInvalidReader
		}
	}

	for i, w := range writers {
		if w == keybase1.PUBLIC_UID {
			return Handle{}, errInvalidWriter
		} else if i > 0 && w.IsTeamOrSubteam() {
			return Handle{}, errInvalidWriter
		}
	}

	// If we have more than one reader, none of them should be the
	// public UID.  And no readers should be a team.
	checkPublic := (len(readers) + len(unresolvedReaders)) > 1
	for _, r := range readers {
		if checkPublic && r == keybase1.PUBLIC_UID {
			return Handle{}, errInvalidReader
		} else if r.IsTeamOrSubteam() {
			return Handle{}, errInvalidReader
		}
	}

	// TODO: Check for overlap between readers and writers, and
	// for duplicates.

	writersCopy := make([]keybase1.UserOrTeamID, len(writers))
	copy(writersCopy, writers)
	sort.Sort(UIDList(writersCopy))

	var readersCopy []keybase1.UserOrTeamID
	if len(readers) > 0 {
		readersCopy = make([]keybase1.UserOrTeamID, len(readers))
		copy(readersCopy, readers)
		sort.Sort(UIDList(readersCopy))
	}

	var unresolvedWritersCopy []keybase1.SocialAssertion
	if len(unresolvedWriters) > 0 {
		unresolvedWritersCopy = make([]keybase1.SocialAssertion, len(unresolvedWriters))
		copy(unresolvedWritersCopy, unresolvedWriters)
		sort.Sort(SocialAssertionList(unresolvedWritersCopy))
	}

	var unresolvedReadersCopy []keybase1.SocialAssertion
	if len(unresolvedReaders) > 0 {
		unresolvedReadersCopy = make([]keybase1.SocialAssertion, len(unresolvedReaders))
		copy(unresolvedReadersCopy, unresolvedReaders)
		sort.Sort(SocialAssertionList(unresolvedReadersCopy))
	}

	conflictInfo, finalizedInfo := HandleExtensionList(extensions).Splat()

	return Handle{
		Writers:           writersCopy,
		Readers:           readersCopy,
		UnresolvedWriters: unresolvedWritersCopy,
		UnresolvedReaders: unresolvedReadersCopy,
		ConflictInfo:      conflictInfo,
		FinalizedInfo:     finalizedInfo,
	}, nil
}

// IsBackedByTeam returns true if h represents a TLF backed by a team. It could
// be either a SingleTeam TLF or a private/public TLF backed by an implicit
// team.
func (h Handle) IsBackedByTeam() bool {
	if len(h.Writers) != 1 ||
		len(h.Readers) != 0 ||
		len(h.UnresolvedReaders) != 0 ||
		len(h.UnresolvedWriters) != 0 {
		return false
	}
	return h.Writers[0].IsTeamOrSubteam()
}

// Type returns the type of TLF this Handle represents.
func (h Handle) Type() Type {
	if len(h.Readers) == 1 &&
		h.Readers[0].Equal(keybase1.PublicUID.AsUserOrTeam()) {
		return Public
	} else if len(h.Writers) == 1 && h.Writers[0].IsTeamOrSubteam() {
		return SingleTeam
	}
	return Private
}

// TypeForKeying returns the keying type for the handle h.
func (h Handle) TypeForKeying() KeyingType {
	if h.IsBackedByTeam() {
		return TeamKeying
	}
	return h.Type().ToKeyingType()
}

func (h Handle) findUserInList(user keybase1.UserOrTeamID,
	users []keybase1.UserOrTeamID) bool {
	for _, u := range users {
		if u == user {
			return true
		}
	}
	return false
}

// IsWriter returns whether or not the given user is a writer for the
// top-level folder represented by this Handle.
func (h Handle) IsWriter(user keybase1.UserOrTeamID) bool {
	if h.TypeForKeying() == TeamKeying {
		panic("Can't call Handle.IsWriter() for a single team TLF")
	}
	return h.findUserInList(user, h.Writers)
}

// IsReader returns whether or not the given user is a reader for the
// top-level folder represented by this Handle.
func (h Handle) IsReader(user keybase1.UserOrTeamID) bool {
	if h.TypeForKeying() == TeamKeying {
		panic("Can't call Handle.IsReader() for a single team TLF")
	}
	return h.TypeForKeying() == PublicKeying ||
		h.findUserInList(user, h.Readers) ||
		h.IsWriter(user)
}

// ResolvedUsers returns the concatenation of h.Writers and h.Readers,
// except if the handle is public, the returned list won't contain
// PUBLIC_UID.
func (h Handle) ResolvedUsers() []keybase1.UserOrTeamID {
	var resolvedUsers []keybase1.UserOrTeamID
	resolvedUsers = append(resolvedUsers, h.Writers...)
	if h.TypeForKeying() == PrivateKeying {
		resolvedUsers = append(resolvedUsers, h.Readers...)
	}
	return resolvedUsers
}

// HasUnresolvedUsers returns true if this handle has any unresolved
// writers or readers.
func (h Handle) HasUnresolvedUsers() bool {
	return len(h.UnresolvedWriters) > 0 || len(h.UnresolvedReaders) > 0
}

// UnresolvedUsers returns the concatenation of h.UnresolvedWriters
// and h.UnresolvedReaders.
func (h Handle) UnresolvedUsers() []keybase1.SocialAssertion {
	var unresolvedUsers []keybase1.SocialAssertion
	unresolvedUsers = append(unresolvedUsers, h.UnresolvedWriters...)
	unresolvedUsers = append(unresolvedUsers, h.UnresolvedReaders...)
	return unresolvedUsers
}

func uidSliceToSet(s []keybase1.UserOrTeamID) map[keybase1.UserOrTeamID]bool {
	m := make(map[keybase1.UserOrTeamID]bool, len(s))
	for _, u := range s {
		m[u] = true
	}
	return m
}

func assertionSliceToSet(s []keybase1.SocialAssertion) map[keybase1.SocialAssertion]bool {
	m := make(map[keybase1.SocialAssertion]bool, len(s))
	for _, u := range s {
		m[u] = true
	}
	return m
}

func resolveAssertions(
	assertions map[keybase1.SocialAssertion]keybase1.UID,
	unresolved []keybase1.SocialAssertion, resolved []keybase1.UserOrTeamID) (
	map[keybase1.UserOrTeamID]bool, []keybase1.SocialAssertion) {
	resolvedMap := uidSliceToSet(resolved)
	unresolvedMap := assertionSliceToSet(unresolved)
	for a, u := range assertions {
		if unresolvedMap[a] {
			resolvedMap[u.AsUserOrTeam()] = true
			delete(unresolvedMap, a)
		}
	}
	return resolvedMap, assertionSetToSlice(unresolvedMap)
}

func uidSetToSlice(m map[keybase1.UserOrTeamID]bool) (
	s []keybase1.UserOrTeamID) {
	for u := range m {
		s = append(s, u)
	}
	return s
}

func assertionSetToSlice(m map[keybase1.SocialAssertion]bool) (s []keybase1.SocialAssertion) {
	for u := range m {
		s = append(s, u)
	}
	return s
}

// ResolveAssertions creates a new Handle given an existing one with
// while resolving the passed assertions.
func (h Handle) ResolveAssertions(
	assertions map[keybase1.SocialAssertion]keybase1.UID) Handle {
	if len(assertions) == 0 || (len(h.UnresolvedWriters) == 0 && len(h.UnresolvedReaders) == 0) || h.IsFinal() {
		return h
	}
	var resolvedWriters, resolvedReaders map[keybase1.UserOrTeamID]bool
	resolvedWriters, h.UnresolvedWriters = resolveAssertions(assertions, h.UnresolvedWriters, h.Writers)
	resolvedReaders, h.UnresolvedReaders = resolveAssertions(assertions, h.UnresolvedReaders, h.Readers)
	h.Writers = uidSetToSlice(resolvedWriters)
	for _, u := range h.Writers {
		delete(resolvedReaders, u)
	}
	h.Readers = uidSetToSlice(resolvedReaders)
	sort.Sort(UIDList(h.Writers))
	sort.Sort(UIDList(h.Readers))
	sort.Sort(SocialAssertionList(h.UnresolvedWriters))
	sort.Sort(SocialAssertionList(h.UnresolvedReaders))
	return h
}

// Extensions returns a list of extensions for the given handle.
func (h Handle) Extensions() (extensions []HandleExtension) {
	if h.ConflictInfo != nil {
		extensions = append(extensions, *h.ConflictInfo)
	}
	if h.FinalizedInfo != nil {
		extensions = append(extensions, *h.FinalizedInfo)
	}
	return extensions
}

// IsFinal returns true if the handle has been finalized.
func (h Handle) IsFinal() bool {
	return h.FinalizedInfo != nil
}

// IsConflict returns true if the handle is a conflict handle.
func (h Handle) IsConflict() bool {
	return h.ConflictInfo != nil
}

// DeepEqual returns true if the handle is equal to another handle.
func (h Handle) DeepEqual(other Handle) bool {
	if len(h.Writers) != len(other.Writers) {
		return false
	}
	if len(h.UnresolvedWriters) != len(other.UnresolvedWriters) {
		return false
	}
	if len(h.Readers) != len(other.Readers) {
		return false
	}
	if len(h.UnresolvedReaders) != len(other.UnresolvedReaders) {
		return false
	}

	if !h.sorted {
		sort.Sort(UIDList(h.Writers))
		sort.Sort(UIDList(h.Readers))
		sort.Sort(SocialAssertionList(h.UnresolvedWriters))
		sort.Sort(SocialAssertionList(h.UnresolvedReaders))
		h.sorted = true
	}
	if !other.sorted {
		sort.Sort(UIDList(other.Writers))
		sort.Sort(UIDList(other.Readers))
		sort.Sort(SocialAssertionList(other.UnresolvedWriters))
		sort.Sort(SocialAssertionList(other.UnresolvedReaders))
		other.sorted = true
	}

	for i, v := range h.Writers {
		if other.Writers[i] != v {
			return false
		}
	}
	for i, v := range h.UnresolvedWriters {
		if other.UnresolvedWriters[i] != v {
			return false
		}
	}
	for i, v := range h.Readers {
		if other.Readers[i] != v {
			return false
		}
	}
	for i, v := range h.UnresolvedReaders {
		if other.UnresolvedReaders[i] != v {
			return false
		}
	}
	if h.IsConflict() != other.IsConflict() {
		return false
	}
	if h.IsFinal() != other.IsFinal() {
		return false
	}
	if h.ConflictInfo != nil &&
		h.ConflictInfo.String() != other.ConflictInfo.String() {
		return false
	}
	if h.FinalizedInfo != nil &&
		h.FinalizedInfo.String() != other.FinalizedInfo.String() {
		return false
	}

	return true
}
<|endoftext|>


<commit_before>package testutil

import (
	"flag"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"testing"
)

var rootEnabled bool

func init() {
	flag.BoolVar(&rootEnabled, "test.root", false, "enable tests that require root")
}

// DumpDir will log out all of the contents of the provided directory to
// testing logger.
//
// Use this in a defer statement within tests that may allocate and exercise a
// temporary directory. Immensely useful for sanity checking and debugging
// failing tests.
//
// One should still test that contents are as expected. This is only a visual
// tool to assist when things don't go your way.
func DumpDir(t *testing.T, root string) {
	if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if fi.Mode()&os.ModeSymlink != 0 {
			target, err := os.Readlink(path)
			if err != nil {
				return err
			}
			t.Log(fi.Mode(), path, "->", target)
		} else if fi.Mode().IsRegular() {
			p, err := ioutil.ReadFile(path)
			if err != nil {
				t.Logf("error reading file: %v", err)
				return nil
			}

			if len(p) > 64 { // just display a little bit.
				p = p[:64]
			}

			t.Log(fi.Mode(), path, "[", strconv.Quote(string(p)), "...]")
		} else {
			t.Log(fi.Mode(), path)
		}

		return nil
	}); err != nil {
		t.Fatalf("error dumping directory: %v", err)
	}
}
<commit_msg>testutil: add Size to DumpDir output<commit_after>package testutil

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"testing"
)

var rootEnabled bool

func init() {
	flag.BoolVar(&rootEnabled, "test.root", false, "enable tests that require root")
}

// DumpDir will log out all of the contents of the provided directory to
// testing logger.
//
// Use this in a defer statement within tests that may allocate and exercise a
// temporary directory. Immensely useful for sanity checking and debugging
// failing tests.
//
// One should still test that contents are as expected. This is only a visual
// tool to assist when things don't go your way.
func DumpDir(t *testing.T, root string) {
	if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if fi.Mode()&os.ModeSymlink != 0 {
			target, err := os.Readlink(path)
			if err != nil {
				return err
			}
			t.Log(fi.Mode(), fmt.Sprintf("%10s", ""), path, "->", target)
		} else if fi.Mode().IsRegular() {
			p, err := ioutil.ReadFile(path)
			if err != nil {
				t.Logf("error reading file: %v", err)
				return nil
			}

			if len(p) > 64 { // just display a little bit.
				p = p[:64]
			}
			t.Log(fi.Mode(), fmt.Sprintf("%10d", fi.Size()), path, "[", strconv.Quote(string(p)), "...]")
		} else {
			t.Log(fi.Mode(), fmt.Sprintf("%10d", fi.Size()), path)
		}

		return nil
	}); err != nil {
		t.Fatalf("error dumping directory: %v", err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Camlistore AUTHORS

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

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

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

/* Package blobpacked registers the "blobpacked" blobserver storage
type, storing blobs initially as one physical blob per logical blob,
but then rearranging little physical blobs into large contiguous blobs
organized by how they'll likely be accessed. An index tracks the
mapping from logical to physical blobs.

Example low-level config:

     "/storage/": {
         "handler": "storage-blobpacked",
         "handlerArgs": {
            "smallBlobs": "/small/",
            "largeBlobs": "/large/",
            "metaIndex": {
               "type": "mysql",
                .....
            }
          }
     }

*/
package blobpacked

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/context"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/pools"
	"camlistore.org/pkg/schema"
	"camlistore.org/pkg/sorted"
	"camlistore.org/pkg/strutil"
)

// TODO: evaluate whether this should even be 0, to keep the schema blobs together at least.
const packThreshold = 512 << 10

type subFetcherStorage interface {
	blobserver.Storage
	blob.SubFetcher
}

type storage struct {
	small blobserver.Storage
	large subFetcherStorage

	// meta key -> value rows are:
	//   sha1-xxxx -> "<size> s"
	//   sha1-xxxx -> "<size> l <big-blobref> <big-offset>"
	meta sorted.KeyValue
}

func (s *storage) String() string {
	return fmt.Sprintf("\"blobpacked\" storage")
}

func newFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (blobserver.Storage, error) {
	var (
		smallPrefix = conf.RequiredString("smallBlobs")
		largePrefix = conf.RequiredString("largeBlobs")
		metaConf    = conf.RequiredObject("metaIndex")
	)
	if err := conf.Validate(); err != nil {
		return nil, err
	}
	small, err := ld.GetStorage(smallPrefix)
	if err != nil {
		return nil, fmt.Errorf("failed to load smallBlobs at %s: %v", smallPrefix, err)
	}
	large, err := ld.GetStorage(largePrefix)
	if err != nil {
		return nil, fmt.Errorf("failed to load largeBlobs at %s: %v", largePrefix, err)
	}
	largeSubber, ok := large.(subFetcherStorage)
	if !ok {
		return nil, fmt.Errorf("largeBlobs at %q of type %T doesn't support fetching sub-ranges of blobs",
			largePrefix, large)
	}
	meta, err := sorted.NewKeyValue(metaConf)
	if err != nil {
		return nil, fmt.Errorf("failed to setup blobpacked metaIndex: %v", err)
	}
	sto := &storage{
		small: small,
		large: largeSubber,
		meta:  meta,
	}
	return sto, nil
}

func init() {
	blobserver.RegisterStorageConstructor("blobpacked", blobserver.StorageConstructor(newFromConfig))
}

func (s *storage) Close() error {
	return nil
}

type meta struct {
	exists   bool
	size     uint32
	largeRef blob.Ref // if invalid, then on small if exists
	largeOff uint32
}

// if not found, err == nil.
func (s *storage) getMetaRow(br blob.Ref) (meta, error) {
	v, err := s.meta.Get(br.String())
	if err == sorted.ErrNotFound {
		return meta{}, nil
	}
	return parseMetaRow([]byte(v))
}

var singleSpace = []byte{' '}

// parses one of:
// "<size> s"
// "<size> l <big-blobref> <big-offset>"
func parseMetaRow(v []byte) (m meta, err error) {
	row := v
	sp := bytes.IndexByte(v, ' ')
	if sp < 1 || sp == len(v)-1 {
		return meta{}, fmt.Errorf("invalid metarow %q", v)
	}
	m.exists = true
	size, err := strutil.ParseUintBytes(v[:sp], 10, 32)
	if err != nil {
		return meta{}, fmt.Errorf("invalid metarow size %q", v)
	}
	m.size = uint32(size)
	v = v[sp+1:]
	switch v[0] {
	default:
		return meta{}, fmt.Errorf("invalid metarow type %q", v)
	case 's':
		if len(v) > 1 {
			return meta{}, fmt.Errorf("invalid small metarow %q", v)
		}
		return
	case 'l':
		if len(v) < 2 || v[1] != ' ' {
			err = errors.New("length")
			break
		}
		v = v[2:] // remains: "<big-blobref> <big-offset>"
		if bytes.Count(v, singleSpace) != 1 {
			err = errors.New("number of spaces")
			break
		}
		sp := bytes.IndexByte(v, ' ')
		largeRef, ok := blob.ParseBytes(v[:sp])
		if !ok {
			err = fmt.Errorf("bad blobref %q", v[:sp])
			break
		}
		m.largeRef = largeRef
		off, err := strutil.ParseUintBytes(v[sp+1:], 10, 32)
		if err != nil {
			break
		}
		m.largeOff = uint32(off)
		return m, nil
	}
	return meta{}, fmt.Errorf("invalid metarow %q: %v", row, err)
}

func (s *storage) ReceiveBlob(br blob.Ref, source io.Reader) (sb blob.SizedRef, err error) {
	buf := pools.BytesBuffer()
	defer pools.PutBuffer(buf)

	if _, err := io.Copy(buf, source); err != nil {
		return sb, err
	}
	size := uint32(buf.Len())
	isFile := false
	fileBlob, err := schema.BlobFromReader(br, bytes.NewReader(buf.Bytes()))
	if err == nil && fileBlob.Type() == "file" {
		isFile = true
	}
	meta, err := s.getMetaRow(br)
	if err != nil {
		return sb, err
	}
	if meta.exists {
		sb = blob.SizedRef{Size: size, Ref: br}
	} else {
		sb, err = s.small.ReceiveBlob(br, buf)
		if err != nil {
			return sb, err
		}
		if err := s.meta.Set(br.String(), fmt.Sprintf("%d s", size)); err != nil {
			return sb, err
		}
	}
	if !isFile || meta.largeRef.Valid() || fileBlob.PartsSize() < packThreshold {
		return sb, nil
	}

	println("TODO: pack into large")
	return sb, nil
}

func (s *storage) Fetch(br blob.Ref) (io.ReadCloser, uint32, error) {
	m, err := s.getMetaRow(br)
	if err != nil {
		return nil, 0, err
	}
	if !m.exists {
		return nil, 0, os.ErrNotExist
	}
	if !m.largeRef.Valid() {
		return s.small.Fetch(br)
	}
	rc, err := s.large.SubFetch(m.largeRef, int64(m.largeOff), int64(m.size))
	if err != nil {
		return nil, 0, err
	}
	return rc, m.size, nil
}

func (s *storage) RemoveBlobs(blobs []blob.Ref) error {
	// TODO: how to support? only delete from index? delete from
	// small if only there?  if in big file, re-break apart into
	// its chunks? no reverse index from big chunk to all its
	// constituent chunks, though. I suppose we could read the chunks
	// from the metadata file in the zip.
	return errors.New("not implemented")
}

func (s *storage) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) (err error) {
	for _, br := range blobs {
		m, err := s.getMetaRow(br)
		if err != nil {
			return err
		}
		if m.exists {
			dest <- blob.SizedRef{Ref: br, Size: m.size}
		}
	}
	return nil
}

func (s *storage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) (err error) {
	defer close(dest)
	t := s.meta.Find(after, "")
	defer func() {
		closeErr := t.Close()
		if err == nil {
			err = closeErr
		}
	}()
	n := 0
	for n < limit && t.Next() {
		if n == 0 && t.Key() == after {
			continue
		}
		n++
		key, val := t.KeyBytes(), t.ValueBytes()
		br, ok := blob.ParseBytes(key)
		if !ok {
			return fmt.Errorf("unknown key %q in meta index", key)
		}
		m, err := parseMetaRow(val)
		if err != nil {
			return err
		}
		if !m.exists {
			panic("should exist if here")
		}
		dest <- blob.SizedRef{Ref: br, Size: m.size}
	}
	return nil
}
<commit_msg>blobpacked: prefix meta rows with 'b:'<commit_after>/*
Copyright 2014 The Camlistore AUTHORS

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

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

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

/* Package blobpacked registers the "blobpacked" blobserver storage
type, storing blobs initially as one physical blob per logical blob,
but then rearranging little physical blobs into large contiguous blobs
organized by how they'll likely be accessed. An index tracks the
mapping from logical to physical blobs.

Example low-level config:

     "/storage/": {
         "handler": "storage-blobpacked",
         "handlerArgs": {
            "smallBlobs": "/small/",
            "largeBlobs": "/large/",
            "metaIndex": {
               "type": "mysql",
                .....
            }
          }
     }

*/
package blobpacked

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/context"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/pools"
	"camlistore.org/pkg/schema"
	"camlistore.org/pkg/sorted"
	"camlistore.org/pkg/strutil"
)

// TODO: evaluate whether this should even be 0, to keep the schema blobs together at least.
// Files under this size aren't packed.
const packThreshold = 512 << 10

// meta key prefixes
const (
	blobMetaPrefix      = "b:"
	blobMetaPrefixLimit = "b;"
)

type subFetcherStorage interface {
	blobserver.Storage
	blob.SubFetcher
}

type storage struct {
	small blobserver.Storage
	large subFetcherStorage

	// meta key -> value rows are:
	//
	// For logical blobs, "b:" prefix:
	//   b:sha1-xxxx -> "<size> s"
	//   b:sha1-xxxx -> "<size> l <big-blobref> <offset_u32>"
	//
	// For wholerefs:
	//   w:sha1-xxxx(wholeref) -> "<nbytes_total_u64> <nchunks_u32>"
	// Then for each big nchunk of the file:
	//   w:sha1-xxxx:0 -> "<big-blobref> <offset_u32> <length_u32>"
	//   w:sha1-xxxx:1 -> "<big-blobref> <offset_u32> <length_u32>"
	//   ...
	meta sorted.KeyValue
}

func (s *storage) String() string {
	return fmt.Sprintf("\"blobpacked\" storage")
}

func newFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (blobserver.Storage, error) {
	var (
		smallPrefix = conf.RequiredString("smallBlobs")
		largePrefix = conf.RequiredString("largeBlobs")
		metaConf    = conf.RequiredObject("metaIndex")
	)
	if err := conf.Validate(); err != nil {
		return nil, err
	}
	small, err := ld.GetStorage(smallPrefix)
	if err != nil {
		return nil, fmt.Errorf("failed to load smallBlobs at %s: %v", smallPrefix, err)
	}
	large, err := ld.GetStorage(largePrefix)
	if err != nil {
		return nil, fmt.Errorf("failed to load largeBlobs at %s: %v", largePrefix, err)
	}
	largeSubber, ok := large.(subFetcherStorage)
	if !ok {
		return nil, fmt.Errorf("largeBlobs at %q of type %T doesn't support fetching sub-ranges of blobs",
			largePrefix, large)
	}
	meta, err := sorted.NewKeyValue(metaConf)
	if err != nil {
		return nil, fmt.Errorf("failed to setup blobpacked metaIndex: %v", err)
	}
	sto := &storage{
		small: small,
		large: largeSubber,
		meta:  meta,
	}
	return sto, nil
}

func init() {
	blobserver.RegisterStorageConstructor("blobpacked", blobserver.StorageConstructor(newFromConfig))
}

func (s *storage) Close() error {
	return nil
}

type meta struct {
	exists   bool
	size     uint32
	largeRef blob.Ref // if invalid, then on small if exists
	largeOff uint32
}

// if not found, err == nil.
func (s *storage) getMetaRow(br blob.Ref) (meta, error) {
	v, err := s.meta.Get(blobMetaPrefix + br.String())
	if err == sorted.ErrNotFound {
		return meta{}, nil
	}
	return parseMetaRow([]byte(v))
}

var singleSpace = []byte{' '}

// parses one of:
// "<size_u32> s"
// "<size_u32> l <big-blobref> <big-offset>"
func parseMetaRow(v []byte) (m meta, err error) {
	row := v
	sp := bytes.IndexByte(v, ' ')
	if sp < 1 || sp == len(v)-1 {
		return meta{}, fmt.Errorf("invalid metarow %q", v)
	}
	m.exists = true
	size, err := strutil.ParseUintBytes(v[:sp], 10, 32)
	if err != nil {
		return meta{}, fmt.Errorf("invalid metarow size %q", v)
	}
	m.size = uint32(size)
	v = v[sp+1:]
	switch v[0] {
	default:
		return meta{}, fmt.Errorf("invalid metarow type %q", v)
	case 's':
		if len(v) > 1 {
			return meta{}, fmt.Errorf("invalid small metarow %q", v)
		}
		return
	case 'l':
		if len(v) < 2 || v[1] != ' ' {
			err = errors.New("length")
			break
		}
		v = v[2:] // remains: "<big-blobref> <big-offset>"
		if bytes.Count(v, singleSpace) != 1 {
			err = errors.New("number of spaces")
			break
		}
		sp := bytes.IndexByte(v, ' ')
		largeRef, ok := blob.ParseBytes(v[:sp])
		if !ok {
			err = fmt.Errorf("bad blobref %q", v[:sp])
			break
		}
		m.largeRef = largeRef
		off, err := strutil.ParseUintBytes(v[sp+1:], 10, 32)
		if err != nil {
			break
		}
		m.largeOff = uint32(off)
		return m, nil
	}
	return meta{}, fmt.Errorf("invalid metarow %q: %v", row, err)
}

func parseMetaRowSizeOnly(v []byte) (size uint32, err error) {
	sp := bytes.IndexByte(v, ' ')
	if sp < 1 || sp == len(v)-1 {
		return 0, fmt.Errorf("invalid metarow %q", v)
	}
	size64, err := strutil.ParseUintBytes(v[:sp], 10, 32)
	if err != nil {
		return 0, fmt.Errorf("invalid metarow size %q", v)
	}
	return uint32(size64), nil
}

func (s *storage) ReceiveBlob(br blob.Ref, source io.Reader) (sb blob.SizedRef, err error) {
	buf := pools.BytesBuffer()
	defer pools.PutBuffer(buf)

	if _, err := io.Copy(buf, source); err != nil {
		return sb, err
	}
	size := uint32(buf.Len())
	isFile := false
	fileBlob, err := schema.BlobFromReader(br, bytes.NewReader(buf.Bytes()))
	if err == nil && fileBlob.Type() == "file" {
		isFile = true
	}
	meta, err := s.getMetaRow(br)
	if err != nil {
		return sb, err
	}
	if meta.exists {
		sb = blob.SizedRef{Size: size, Ref: br}
	} else {
		sb, err = s.small.ReceiveBlob(br, buf)
		if err != nil {
			return sb, err
		}
		if err := s.meta.Set(blobMetaPrefix+br.String(), fmt.Sprintf("%d s", size)); err != nil {
			return sb, err
		}
	}
	if !isFile || meta.largeRef.Valid() || fileBlob.PartsSize() < packThreshold {
		return sb, nil
	}

	println("TODO: pack into large")
	return sb, nil
}

func (s *storage) Fetch(br blob.Ref) (io.ReadCloser, uint32, error) {
	m, err := s.getMetaRow(br)
	if err != nil {
		return nil, 0, err
	}
	if !m.exists {
		return nil, 0, os.ErrNotExist
	}
	if !m.largeRef.Valid() {
		return s.small.Fetch(br)
	}
	rc, err := s.large.SubFetch(m.largeRef, int64(m.largeOff), int64(m.size))
	if err != nil {
		return nil, 0, err
	}
	return rc, m.size, nil
}

func (s *storage) RemoveBlobs(blobs []blob.Ref) error {
	// TODO: how to support? only delete from index? delete from
	// small if only there?  if in big file, re-break apart into
	// its chunks? no reverse index from big chunk to all its
	// constituent chunks, though. I suppose we could read the chunks
	// from the metadata file in the zip.
	return errors.New("not implemented")
}

func (s *storage) StatBlobs(dest chan<- blob.SizedRef, blobs []blob.Ref) (err error) {
	for _, br := range blobs {
		m, err := s.getMetaRow(br)
		if err != nil {
			return err
		}
		if m.exists {
			dest <- blob.SizedRef{Ref: br, Size: m.size}
		}
	}
	return nil
}

func (s *storage) EnumerateBlobs(ctx *context.Context, dest chan<- blob.SizedRef, after string, limit int) (err error) {
	defer close(dest)
	t := s.meta.Find(blobMetaPrefix+after, blobMetaPrefixLimit)
	defer func() {
		closeErr := t.Close()
		if err == nil {
			err = closeErr
		}
	}()
	n := 0
	afterb := []byte(after)
	for n < limit && t.Next() {
		key := t.KeyBytes()[len(blobMetaPrefix):]
		if n == 0 && bytes.Equal(key, afterb) {
			continue
		}
		n++
		br, ok := blob.ParseBytes(key)
		if !ok {
			return fmt.Errorf("unknown key %q in meta index", t.Key())
		}
		size, err := parseMetaRowSizeOnly(t.ValueBytes())
		if err != nil {
			return err
		}
		dest <- blob.SizedRef{Ref: br, Size: size}
	}
	return nil
}
<|endoftext|>


<commit_before>package bitbucketapi

import (
	"fmt"
	"testing"

	"github.com/golang-jwt/jwt/v4"
	"github.com/stretchr/testify/assert"
)

func TestPushEventChangeObjectTargetAuthorUser(t *testing.T) {
	t.Run("GetEmailAddressExtractsEmailAddressFromRawField", func(t *testing.T) {

		u := PushEventChangeObjectTargetAuthor{
			Raw: "Someones Name \u003csomeone@server.com\u003e",
		}

		// act
		emailAddress := u.GetEmailAddress()

		assert.Equal(t, "someone@server.com", emailAddress)
	})
}

func TestAuthor(t *testing.T) {
	t.Run("GetEmailAddressExtractsEmailAddressFromRawField", func(t *testing.T) {

		u := Author{
			Raw: "Someones Name \u003csomeone@server.com\u003e",
		}

		// act
		emailAddress := u.GetEmailAddress()

		assert.Equal(t, "someone@server.com", emailAddress)
	})

	t.Run("GetNameFromRawField", func(t *testing.T) {

		u := Author{
			Raw: "Someones Name \u003csomeone@server.com\u003e",
		}

		// act
		emailAddress := u.GetName()

		assert.Equal(t, "Someones Name", emailAddress)
	})
}

func TestPushEventChangeObjectTarget(t *testing.T) {
	t.Run("GetCommitMessageExtractsTitleFromMessageField", func(t *testing.T) {

		c := PushEventChangeObjectTarget{
			Message: "log api call response body on error only\n\nmore detail",
		}

		// act
		message := c.GetCommitMessage()

		assert.Equal(t, "log api call response body on error only", message)
	})
}

func TestCommit(t *testing.T) {
	t.Run("GetCommitMessageExtractsTitleFromMessageField", func(t *testing.T) {

		c := Commit{
			Message: "log api call response body on error only\n\nmore detail",
		}

		// act
		message := c.GetCommitMessage()

		assert.Equal(t, "log api call response body on error only", message)
	})
}

func TestGetWorkspaceUUID(t *testing.T) {
	t.Run("ExtractUUIDFromClientKey", func(t *testing.T) {
		bai := BitbucketAppInstallation{
			ClientKey: "ari:cloud:bitbucket::app/{f786eeda-2b64-4617-8039-a85895b13619}/estafette-ci",
		}

		// act
		uuid := bai.GetWorkspaceUUID()

		assert.Equal(t, "{f786eeda-2b64-4617-8039-a85895b13619}", uuid)
	})
}

func TestValidationErrorIssuedAt(t *testing.T) {
	t.Run("HasNoRemainingErrorsIfOnlyErrorIsValidationErrorIssuedAt", func(t *testing.T) {
		vErr := new(jwt.ValidationError)
		vErr.Inner = fmt.Errorf("Token used before issued")
		vErr.Errors |= jwt.ValidationErrorIssuedAt

		var err error
		err = vErr

		validationErr, isValidationError := err.(*jwt.ValidationError)
		hasIssuedAtError := isValidationError && validationErr.Errors&jwt.ValidationErrorIssuedAt != 0
		remainingErrors := validationErr.Errors ^ jwt.ValidationErrorIssuedAt

		assert.False(t, vErr.Errors == 0)
		assert.True(t, isValidationError)
		assert.True(t, hasIssuedAtError)
		assert.Equal(t, uint32(0), remainingErrors)
	})

	t.Run("HasRemainingErrorsIfOtherErrorsBesidesValidationErrorIssuedAt", func(t *testing.T) {
		vErr := new(jwt.ValidationError)
		vErr.Inner = fmt.Errorf("Token used before issued")
		vErr.Errors |= jwt.ValidationErrorIssuedAt
		vErr.Errors |= jwt.ValidationErrorExpired

		var err error
		err = vErr

		validationErr, isValidationError := err.(*jwt.ValidationError)
		hasIssuedAtError := isValidationError && validationErr.Errors&jwt.ValidationErrorIssuedAt != 0
		remainingErrors := validationErr.Errors ^ jwt.ValidationErrorIssuedAt

		assert.False(t, vErr.Errors == 0)
		assert.True(t, isValidationError)
		assert.True(t, hasIssuedAtError)
		assert.NotEqual(t, uint32(0), remainingErrors)
	})
}
<commit_msg>fix linting issue<commit_after>package bitbucketapi

import (
	"fmt"
	"testing"

	"github.com/golang-jwt/jwt/v4"
	"github.com/stretchr/testify/assert"
)

func TestPushEventChangeObjectTargetAuthorUser(t *testing.T) {
	t.Run("GetEmailAddressExtractsEmailAddressFromRawField", func(t *testing.T) {

		u := PushEventChangeObjectTargetAuthor{
			Raw: "Someones Name \u003csomeone@server.com\u003e",
		}

		// act
		emailAddress := u.GetEmailAddress()

		assert.Equal(t, "someone@server.com", emailAddress)
	})
}

func TestAuthor(t *testing.T) {
	t.Run("GetEmailAddressExtractsEmailAddressFromRawField", func(t *testing.T) {

		u := Author{
			Raw: "Someones Name \u003csomeone@server.com\u003e",
		}

		// act
		emailAddress := u.GetEmailAddress()

		assert.Equal(t, "someone@server.com", emailAddress)
	})

	t.Run("GetNameFromRawField", func(t *testing.T) {

		u := Author{
			Raw: "Someones Name \u003csomeone@server.com\u003e",
		}

		// act
		emailAddress := u.GetName()

		assert.Equal(t, "Someones Name", emailAddress)
	})
}

func TestPushEventChangeObjectTarget(t *testing.T) {
	t.Run("GetCommitMessageExtractsTitleFromMessageField", func(t *testing.T) {

		c := PushEventChangeObjectTarget{
			Message: "log api call response body on error only\n\nmore detail",
		}

		// act
		message := c.GetCommitMessage()

		assert.Equal(t, "log api call response body on error only", message)
	})
}

func TestCommit(t *testing.T) {
	t.Run("GetCommitMessageExtractsTitleFromMessageField", func(t *testing.T) {

		c := Commit{
			Message: "log api call response body on error only\n\nmore detail",
		}

		// act
		message := c.GetCommitMessage()

		assert.Equal(t, "log api call response body on error only", message)
	})
}

func TestGetWorkspaceUUID(t *testing.T) {
	t.Run("ExtractUUIDFromClientKey", func(t *testing.T) {
		bai := BitbucketAppInstallation{
			ClientKey: "ari:cloud:bitbucket::app/{f786eeda-2b64-4617-8039-a85895b13619}/estafette-ci",
		}

		// act
		uuid := bai.GetWorkspaceUUID()

		assert.Equal(t, "{f786eeda-2b64-4617-8039-a85895b13619}", uuid)
	})
}

func TestValidationErrorIssuedAt(t *testing.T) {
	t.Run("HasNoRemainingErrorsIfOnlyErrorIsValidationErrorIssuedAt", func(t *testing.T) {
		vErr := new(jwt.ValidationError)
		vErr.Inner = fmt.Errorf("Token used before issued")
		vErr.Errors |= jwt.ValidationErrorIssuedAt

		err := error(vErr)

		validationErr, isValidationError := err.(*jwt.ValidationError)
		hasIssuedAtError := isValidationError && validationErr.Errors&jwt.ValidationErrorIssuedAt != 0
		remainingErrors := validationErr.Errors ^ jwt.ValidationErrorIssuedAt

		assert.False(t, vErr.Errors == 0)
		assert.True(t, isValidationError)
		assert.True(t, hasIssuedAtError)
		assert.Equal(t, uint32(0), remainingErrors)
	})

	t.Run("HasRemainingErrorsIfOtherErrorsBesidesValidationErrorIssuedAt", func(t *testing.T) {
		vErr := new(jwt.ValidationError)
		vErr.Inner = fmt.Errorf("Token used before issued")
		vErr.Errors |= jwt.ValidationErrorIssuedAt
		vErr.Errors |= jwt.ValidationErrorExpired

		err := error(vErr)

		validationErr, isValidationError := err.(*jwt.ValidationError)
		hasIssuedAtError := isValidationError && validationErr.Errors&jwt.ValidationErrorIssuedAt != 0
		remainingErrors := validationErr.Errors ^ jwt.ValidationErrorIssuedAt

		assert.False(t, vErr.Errors == 0)
		assert.True(t, isValidationError)
		assert.True(t, hasIssuedAtError)
		assert.NotEqual(t, uint32(0), remainingErrors)
	})
}
<|endoftext|>


<commit_before>package gollection

import (
	"fmt"
	"reflect"
)

func (g *gollection) Filter(f interface{}) *gollection {
	if g.err != nil {
		return &gollection{err: g.err}
	}

	if g.ch != nil {
		return g.filterStream(f)
	}
	return g.filter(f)
}

func (g *gollection) filter(f interface{}) *gollection {
	sv := reflect.ValueOf(g.slice)
	if sv.Kind() != reflect.Slice {
		return &gollection{
			slice: nil,
			err:   fmt.Errorf("gollection.Filter called with non-slice value of type %T", g.slice),
		}
	}

	funcValue := reflect.ValueOf(f)
	funcType := funcValue.Type()
	if funcType.Kind() != reflect.Func || funcType.NumIn() != 1 || funcType.NumOut() != 1 || funcType.Out(0).Kind() != reflect.Bool {
		return &gollection{
			slice: nil,
			err:   fmt.Errorf("gollection.Filter called with invalid func. required func(in <T>) bool but supplied %v", g.slice),
		}
	}

	resultSliceType := reflect.SliceOf(funcType.In(0))
	ret := reflect.MakeSlice(resultSliceType, 0, sv.Len())

	for i := 0; i < sv.Len(); i++ {
		v := sv.Index(i)
		if funcValue.Call([]reflect.Value{v})[0].Interface().(bool) {
			ret = reflect.Append(ret, v)
		}
	}

	return &gollection{
		slice: ret.Interface(),
	}
}

func (g *gollection) filterStream(f interface{}) *gollection {
	next := &gollection{
		ch: make(chan interface{}),
	}

	funcValue := reflect.ValueOf(f)
	funcType := funcValue.Type()
	if funcType.Kind() != reflect.Func || funcType.NumIn() != 1 || funcType.NumOut() != 1 || funcType.Out(0).Kind() != reflect.Bool {
		return &gollection{
			err: fmt.Errorf("gollection.Filter called with invalid func. required func(in <T>) bool but supplied %v", f),
		}
	}

	var initialized bool
	go func() {
		for {
			select {
			case v, ok := <-g.ch:
				if ok {
					if !initialized {
						// initialize next stream type
						next.ch <- reflect.SliceOf(funcType.In(0))
						initialized = true
						continue
					}

					if funcValue.Call([]reflect.Value{reflect.ValueOf(v)})[0].Interface().(bool) {
						next.ch <- v
					}
				} else {
					close(next.ch)
					return
				}
			default:
				continue
			}
		}
	}()
	return next
}
<commit_msg>refactor filter<commit_after>package gollection

import (
	"fmt"
	"reflect"
)

func (g *gollection) Filter(f interface{}) *gollection {
	if g.err != nil {
		return &gollection{err: g.err}
	}

	if g.ch != nil {
		return g.filterStream(f)
	}
	return g.filter(f)
}

func (g *gollection) validateFilterFunc(f interface{}) (reflect.Value, reflect.Type, error) {
	funcValue := reflect.ValueOf(f)
	funcType := funcValue.Type()
	if funcType.Kind() != reflect.Func || funcType.NumIn() != 1 || funcType.NumOut() != 1 || funcType.Out(0).Kind() != reflect.Bool {
		return reflect.Value{}, nil, fmt.Errorf("gollection.Filter called with invalid func. required func(in <T>) bool but supplied %v", funcType)
	}
	return funcValue, funcType, nil
}

func (g *gollection) validateSlice(funcName string) (reflect.Value, error) {
	sv := reflect.ValueOf(g.slice)
	if sv.Kind() != reflect.Slice {
		return reflect.Value{}, fmt.Errorf("gollection.%s called with non-slice value of type %T", funcName, g.slice)
	}
	return sv, nil
}

func (g *gollection) filter(f interface{}) *gollection {
	sv, err := g.validateSlice("Filter")
	if err != nil {
		return &gollection{
			err: err,
		}
	}

	funcValue, funcType, err := g.validateFilterFunc(f)
	if err != nil {
		return &gollection{
			err: err,
		}
	}

	resultSliceType := reflect.SliceOf(funcType.In(0))
	ret := reflect.MakeSlice(resultSliceType, 0, sv.Len())

	for i := 0; i < sv.Len(); i++ {
		v := sv.Index(i)
		if funcValue.Call([]reflect.Value{v})[0].Interface().(bool) {
			ret = reflect.Append(ret, v)
		}
	}

	return &gollection{
		slice: ret.Interface(),
	}
}

func (g *gollection) filterStream(f interface{}) *gollection {
	next := &gollection{
		ch: make(chan interface{}),
	}

	funcValue, funcType, err := g.validateFilterFunc(f)
	if err != nil {
		return &gollection{
			err: err,
		}
	}

	var initialized bool
	go func() {
		for {
			select {
			case v, ok := <-g.ch:
				if ok {
					if !initialized {
						// initialize next stream type
						next.ch <- reflect.SliceOf(funcType.In(0))
						initialized = true
						continue
					}

					if funcValue.Call([]reflect.Value{reflect.ValueOf(v)})[0].Interface().(bool) {
						next.ch <- v
					}
				} else {
					close(next.ch)
					return
				}
			default:
				continue
			}
		}
	}()
	return next
}
<|endoftext|>


<commit_before>package database

import (
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"

	"github.com/maxmind/geoipupdate/pkg/geoipupdate"
	"github.com/pkg/errors"
)

// HTTPDatabaseReader is a Reader that uses an HTTP client to retrieve
// databases.
type HTTPDatabaseReader struct {
	client            *http.Client
	url               string
	licenseKey        string
	accountID         int
	preserveFileTimes bool
	verbose           bool
}

// NewHTTPDatabaseReader creates a Reader that downloads database updates via
// HTTP.
func NewHTTPDatabaseReader(client *http.Client, config *geoipupdate.Config) Reader {
	return &HTTPDatabaseReader{
		client:            client,
		url:               config.URL,
		licenseKey:        config.LicenseKey,
		accountID:         config.AccountID,
		preserveFileTimes: config.PreserveFileTimes,
		verbose:           config.Verbose,
	}
}

// Get retrieves the given edition ID using an HTTP client, writes it to the
// Writer, and validates the hash before committing.
func (reader *HTTPDatabaseReader) Get(destination Writer, editionID string) error {
	defer func() {
		if err := destination.Close(); err != nil {
			log.Println(err)
		}
	}()
	lastHash, err := destination.GetHash()
	if err != nil {
		return errors.Wrap(err, "unable to get previous hash")
	}
	maxMindURL := fmt.Sprintf(
		"%s/geoip/databases/%s/update?db_md5=%s",
		reader.url,
		url.PathEscape(editionID),
		url.QueryEscape(lastHash),
	)

	req, err := http.NewRequest(http.MethodGet, maxMindURL, nil)
	if err != nil {
		return errors.Wrap(err, "error creating request")
	}
	if reader.accountID != 0 {
		req.SetBasicAuth(fmt.Sprintf("%d", reader.accountID), reader.licenseKey)
	}

	if reader.verbose {
		log.Printf("Performing update request to %s", maxMindURL)
	}
	response, err := reader.client.Do(req)
	if err != nil {
		return errors.Wrap(err, "error performing HTTP request")
	}
	defer func() {
		if err := response.Body.Close(); err != nil {
			log.Fatalf("Error closing response body: %+v", errors.Wrap(err, "closing body"))
		}
	}()

	if response.StatusCode == http.StatusNotModified {
		if reader.verbose {
			log.Printf("No new updates available for %s", editionID)
		}
		return nil
	}

	if response.StatusCode != http.StatusOK {
		buf, err := ioutil.ReadAll(io.LimitReader(response.Body, 256))
		if err == nil {
			return errors.Errorf("unexpected HTTP status code: %s: %s", response.Status, buf)
		}
		return errors.Errorf("unexpected HTTP status code: %s", response.Status)
	}

	newMD5 := response.Header.Get("X-Database-MD5")
	if newMD5 == "" {
		return errors.New("no X-Database-MD5 header found")
	}

	gzReader, err := gzip.NewReader(response.Body)
	if err != nil {
		return errors.Wrap(err, "encountered an error creating GZIP reader")
	}
	defer func() {
		if err := gzReader.Close(); err != nil {
			log.Printf("error closing gzip reader: %s", err)
		}
	}()

	if _, err = io.Copy(destination, gzReader); err != nil {
		return errors.Wrap(err, "error writing response")
	}

	if err := destination.ValidHash(newMD5); err != nil {
		return err
	}

	if err := destination.Commit(); err != nil {
		return errors.Wrap(err, "encountered an issue committing database update")
	}

	if reader.preserveFileTimes {
		modificationTime, err := lastModified(response.Header.Get("Last-Modified"))
		if err != nil {
			return errors.Wrap(err, "unable to get last modified time")
		}
		err = destination.SetFileModificationTime(modificationTime)
		if err != nil {
			return errors.Wrap(err, "unable to set modification time")
		}
	}

	return nil
}

// LastModified retrieves the date that the MaxMind database was last modified.
func lastModified(lastModified string) (time.Time, error) {
	if lastModified == "" {
		return time.Time{}, errors.New("no Last-Modified header found")
	}

	t, err := time.ParseInLocation(time.RFC1123, lastModified, time.UTC)
	if err != nil {
		return time.Time{}, errors.Wrap(err, "error parsing time")
	}

	return t, nil
}
<commit_msg>Move variable closer to its use<commit_after>package database

import (
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"

	"github.com/maxmind/geoipupdate/pkg/geoipupdate"
	"github.com/pkg/errors"
)

// HTTPDatabaseReader is a Reader that uses an HTTP client to retrieve
// databases.
type HTTPDatabaseReader struct {
	client            *http.Client
	url               string
	licenseKey        string
	accountID         int
	preserveFileTimes bool
	verbose           bool
}

// NewHTTPDatabaseReader creates a Reader that downloads database updates via
// HTTP.
func NewHTTPDatabaseReader(client *http.Client, config *geoipupdate.Config) Reader {
	return &HTTPDatabaseReader{
		client:            client,
		url:               config.URL,
		licenseKey:        config.LicenseKey,
		accountID:         config.AccountID,
		preserveFileTimes: config.PreserveFileTimes,
		verbose:           config.Verbose,
	}
}

// Get retrieves the given edition ID using an HTTP client, writes it to the
// Writer, and validates the hash before committing.
func (reader *HTTPDatabaseReader) Get(destination Writer, editionID string) error {
	defer func() {
		if err := destination.Close(); err != nil {
			log.Println(err)
		}
	}()
	lastHash, err := destination.GetHash()
	if err != nil {
		return errors.Wrap(err, "unable to get previous hash")
	}
	maxMindURL := fmt.Sprintf(
		"%s/geoip/databases/%s/update?db_md5=%s",
		reader.url,
		url.PathEscape(editionID),
		url.QueryEscape(lastHash),
	)

	req, err := http.NewRequest(http.MethodGet, maxMindURL, nil)
	if err != nil {
		return errors.Wrap(err, "error creating request")
	}
	if reader.accountID != 0 {
		req.SetBasicAuth(fmt.Sprintf("%d", reader.accountID), reader.licenseKey)
	}

	if reader.verbose {
		log.Printf("Performing update request to %s", maxMindURL)
	}
	response, err := reader.client.Do(req)
	if err != nil {
		return errors.Wrap(err, "error performing HTTP request")
	}
	defer func() {
		if err := response.Body.Close(); err != nil {
			log.Fatalf("Error closing response body: %+v", errors.Wrap(err, "closing body"))
		}
	}()

	if response.StatusCode == http.StatusNotModified {
		if reader.verbose {
			log.Printf("No new updates available for %s", editionID)
		}
		return nil
	}

	if response.StatusCode != http.StatusOK {
		buf, err := ioutil.ReadAll(io.LimitReader(response.Body, 256))
		if err == nil {
			return errors.Errorf("unexpected HTTP status code: %s: %s", response.Status, buf)
		}
		return errors.Errorf("unexpected HTTP status code: %s", response.Status)
	}

	gzReader, err := gzip.NewReader(response.Body)
	if err != nil {
		return errors.Wrap(err, "encountered an error creating GZIP reader")
	}
	defer func() {
		if err := gzReader.Close(); err != nil {
			log.Printf("error closing gzip reader: %s", err)
		}
	}()

	if _, err = io.Copy(destination, gzReader); err != nil {
		return errors.Wrap(err, "error writing response")
	}

	newMD5 := response.Header.Get("X-Database-MD5")
	if newMD5 == "" {
		return errors.New("no X-Database-MD5 header found")
	}
	if err := destination.ValidHash(newMD5); err != nil {
		return err
	}

	if err := destination.Commit(); err != nil {
		return errors.Wrap(err, "encountered an issue committing database update")
	}

	if reader.preserveFileTimes {
		modificationTime, err := lastModified(response.Header.Get("Last-Modified"))
		if err != nil {
			return errors.Wrap(err, "unable to get last modified time")
		}
		err = destination.SetFileModificationTime(modificationTime)
		if err != nil {
			return errors.Wrap(err, "unable to set modification time")
		}
	}

	return nil
}

// LastModified retrieves the date that the MaxMind database was last modified.
func lastModified(lastModified string) (time.Time, error) {
	if lastModified == "" {
		return time.Time{}, errors.New("no Last-Modified header found")
	}

	t, err := time.ParseInLocation(time.RFC1123, lastModified, time.UTC)
	if err != nil {
		return time.Time{}, errors.Wrap(err, "error parsing time")
	}

	return t, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package context

import (
	"io"
	"os"
	"path/filepath"

	"bytes"

	"github.com/juju/errors"
	"github.com/juju/juju/resource"
	"github.com/juju/utils/hash"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
)

const HookContextFacade = resource.ComponentName + "-hook-context"

type APIClient interface {
	GetResource(resourceName string) (resource.Resource, io.ReadCloser, error)
}

func NewContextAPI(apiClient APIClient, unitDataDirPath string) *Context {
	return &Context{
		apiClient:       apiClient,
		unitDataDirPath: unitDataDirPath,
	}
}

type Context struct {
	apiClient       APIClient
	unitDataDirPath string
}

func (c *Context) DownloadResource(name string) (string, error) {
	// TODO(katco): Check to see if we have latest version

	checksumWriter := charmresource.NewFingerprintHash()

	resourceInfo, resourceReader, err := c.apiClient.GetResource(name)
	if err != nil {
		return "", errors.Trace(err)
	}
	defer resourceReader.Close()

	resourcePath := resolveResourcePath(c.unitDataDirPath, resourceInfo)
	hashingReader := hash.NewHashingReader(resourceReader, checksumWriter)
	if err := downloadAndWriteResourceToDisk(resourcePath, hashingReader); err != nil {
		return "", errors.Trace(err)
	}
	if bytes.Equal(checksumWriter.Fingerprint().Bytes(), resourceInfo.Fingerprint.Bytes()) == false {
		return "", errors.Errorf("resource fingerprint does not match expected (%q != %q)", checksumWriter.Fingerprint(), resourceInfo.Fingerprint.Bytes())
	}

	return resourcePath, nil
}

func (c *Context) Flush() error {
	return nil
}

type HookContext interface {
	DownloadResource(name string) (filePath string, _ error)
}

func ResourceExistsOnFilesystem(resourcePath string) (bool, error) {

	if _, err := os.Stat(resourcePath); err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, errors.Trace(err)
	}
	return true, nil
}

func resolveResourcePath(unitPath string, resourceInfo resource.Resource) string {
	return filepath.Join(unitPath, resourceInfo.Name, resourceInfo.Path)
}

func downloadAndWriteResourceToDisk(resourcePath string, resourceReader io.Reader) error {
	// TODO(katco): Potential race-condition: two commands running at
	// once.
	if err := os.MkdirAll(filepath.Dir(resourcePath), 0755); err != nil {
		return errors.Annotate(err, "could not create resource dir")
	}
	resourceHandle, err := os.OpenFile(resourcePath, os.O_CREATE, 0644)
	if err != nil {
		return errors.Annotate(err, "could not create new file for resource")
	}

	// TODO(ericsnow) This needs to be atomic?
	// (e.g. write to separate dir and move the dir into place)
	if _, err := io.Copy(resourceHandle, resourceReader); err != nil {
		return errors.Annotate(err, "could not write resource to file")
	}

	return nil
}
<commit_msg>Use os.Create() instead of os.OpenFile().<commit_after>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package context

import (
	"io"
	"os"
	"path/filepath"

	"bytes"

	"github.com/juju/errors"
	"github.com/juju/juju/resource"
	"github.com/juju/utils/hash"
	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
)

const HookContextFacade = resource.ComponentName + "-hook-context"

type APIClient interface {
	GetResource(resourceName string) (resource.Resource, io.ReadCloser, error)
}

func NewContextAPI(apiClient APIClient, unitDataDirPath string) *Context {
	return &Context{
		apiClient:       apiClient,
		unitDataDirPath: unitDataDirPath,
	}
}

type Context struct {
	apiClient       APIClient
	unitDataDirPath string
}

func (c *Context) DownloadResource(name string) (string, error) {
	// TODO(katco): Check to see if we have latest version

	checksumWriter := charmresource.NewFingerprintHash()

	resourceInfo, resourceReader, err := c.apiClient.GetResource(name)
	if err != nil {
		return "", errors.Trace(err)
	}
	defer resourceReader.Close()

	resourcePath := resolveResourcePath(c.unitDataDirPath, resourceInfo)
	hashingReader := hash.NewHashingReader(resourceReader, checksumWriter)
	if err := downloadAndWriteResourceToDisk(resourcePath, hashingReader); err != nil {
		return "", errors.Trace(err)
	}
	if bytes.Equal(checksumWriter.Fingerprint().Bytes(), resourceInfo.Fingerprint.Bytes()) == false {
		return "", errors.Errorf("resource fingerprint does not match expected (%q != %q)", checksumWriter.Fingerprint(), resourceInfo.Fingerprint.Bytes())
	}

	return resourcePath, nil
}

func (c *Context) Flush() error {
	return nil
}

type HookContext interface {
	DownloadResource(name string) (filePath string, _ error)
}

func ResourceExistsOnFilesystem(resourcePath string) (bool, error) {

	if _, err := os.Stat(resourcePath); err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, errors.Trace(err)
	}
	return true, nil
}

func resolveResourcePath(unitPath string, resourceInfo resource.Resource) string {
	return filepath.Join(unitPath, resourceInfo.Name, resourceInfo.Path)
}

func downloadAndWriteResourceToDisk(resourcePath string, resourceReader io.Reader) error {
	// TODO(ericsnow) This needs to be atomic?
	// (e.g. write to separate dir and move the dir into place)
	// TODO(katco): Potential race-condition: two commands running at
	// once.
	if err := os.MkdirAll(filepath.Dir(resourcePath), 0755); err != nil {
		return errors.Annotate(err, "could not create resource dir")
	}
	resourceHandle, err := os.Create(resourcePath)
	if err != nil {
		return errors.Annotate(err, "could not create new file for resource")
	}

	if _, err := io.Copy(resourceHandle, resourceReader); err != nil {
		return errors.Annotate(err, "could not write resource to file")
	}

	return nil
}
<|endoftext|>


<commit_before>//go:build openbsd
// +build openbsd

package kernel

import (
	"fmt"
	"runtime'
)

// A stub called by kernel_unix.go .
func uname() (*Utsname, error) {
	return nil, fmt.Errorf("Kernel version detection is not available on %s", runtime.GOOS)
}
<commit_msg>Fix a typo that breaks build / vedoring<commit_after>//go:build openbsd
// +build openbsd

package kernel

import (
	"fmt"
	"runtime"
)

// A stub called by kernel_unix.go .
func uname() (*Utsname, error) {
	return nil, fmt.Errorf("Kernel version detection is not available on %s", runtime.GOOS)
}
<|endoftext|>


<commit_before>package aws

import (
	"bytes"
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

var taskDefinitionRE = regexp.MustCompile("^([a-zA-Z0-9_-]+):([0-9]+)$")

func resourceAwsEcsService() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEcsServiceCreate,
		Read:   resourceAwsEcsServiceRead,
		Update: resourceAwsEcsServiceUpdate,
		Delete: resourceAwsEcsServiceDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cluster": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"task_definition": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"desired_count": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
			},

			"iam_role": &schema.Schema{
				Type:     schema.TypeString,
				ForceNew: true,
				Optional: true,
			},

			"deployment_maximum_percent": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  200,
			},

			"deployment_minimum_healthy_percent": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  100,
			},

			"load_balancer": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"elb_name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},

						"container_name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},

						"container_port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
							ForceNew: true,
						},
					},
				},
				Set: resourceAwsEcsLoadBalancerHash,
			},
		},
	}
}

func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	input := ecs.CreateServiceInput{
		ServiceName:    aws.String(d.Get("name").(string)),
		TaskDefinition: aws.String(d.Get("task_definition").(string)),
		DesiredCount:   aws.Int64(int64(d.Get("desired_count").(int))),
		ClientToken:    aws.String(resource.UniqueId()),
		DeploymentConfiguration: &ecs.DeploymentConfiguration{
			MaximumPercent:        aws.Int64(int64(d.Get("deployment_maximum_percent").(int))),
			MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))),
		},
	}

	if v, ok := d.GetOk("cluster"); ok {
		input.Cluster = aws.String(v.(string))
	}

	loadBalancers := expandEcsLoadBalancers(d.Get("load_balancer").(*schema.Set).List())
	if len(loadBalancers) > 0 {
		log.Printf("[DEBUG] Adding ECS load balancers: %s", loadBalancers)
		input.LoadBalancers = loadBalancers
	}
	if v, ok := d.GetOk("iam_role"); ok {
		input.Role = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating ECS service: %s", input)

	// Retry due to AWS IAM policy eventual consistency
	// See https://github.com/hashicorp/terraform/issues/2869
	var out *ecs.CreateServiceOutput
	var err error
	err = resource.Retry(2*time.Minute, func() *resource.RetryError {
		out, err = conn.CreateService(&input)

		if err != nil {
			ec2err, ok := err.(awserr.Error)
			if !ok {
				return resource.NonRetryableError(err)
			}
			if ec2err.Code() == "InvalidParameterException" {
				log.Printf("[DEBUG] Trying to create ECS service again: %q",
					ec2err.Message())
				return resource.RetryableError(err)
			}

			return resource.NonRetryableError(err)
		}

		return nil
	})
	if err != nil {
		return err
	}

	service := *out.Service

	log.Printf("[DEBUG] ECS service created: %s", *service.ServiceArn)
	d.SetId(*service.ServiceArn)

	return resourceAwsEcsServiceUpdate(d, meta)
}

func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	log.Printf("[DEBUG] Reading ECS service %s", d.Id())
	input := ecs.DescribeServicesInput{
		Services: []*string{aws.String(d.Id())},
		Cluster:  aws.String(d.Get("cluster").(string)),
	}

	out, err := conn.DescribeServices(&input)
	if err != nil {
		return err
	}

	if len(out.Services) < 1 {
		log.Printf("[DEBUG] Removing ECS service %s (%s) because it's gone", d.Get("name").(string), d.Id())
		d.SetId("")
		return nil
	}

	service := out.Services[0]

	// Status==INACTIVE means deleted service
	if *service.Status == "INACTIVE" {
		log.Printf("[DEBUG] Removing ECS service %q because it's INACTIVE", *service.ServiceArn)
		d.SetId("")
		return nil
	}

	log.Printf("[DEBUG] Received ECS service %s", service)

	d.SetId(*service.ServiceArn)
	d.Set("name", *service.ServiceName)

	// Save task definition in the same format
	if strings.HasPrefix(d.Get("task_definition").(string), "arn:aws:ecs:") {
		d.Set("task_definition", *service.TaskDefinition)
	} else {
		taskDefinition := buildFamilyAndRevisionFromARN(*service.TaskDefinition)
		d.Set("task_definition", taskDefinition)
	}

	d.Set("desired_count", *service.DesiredCount)

	// Save cluster in the same format
	if strings.HasPrefix(d.Get("cluster").(string), "arn:aws:ecs:") {
		d.Set("cluster", *service.ClusterArn)
	} else {
		clusterARN := getNameFromARN(*service.ClusterArn)
		d.Set("cluster", clusterARN)
	}

	// Save IAM role in the same format
	if service.RoleArn != nil {
		if strings.HasPrefix(d.Get("iam_role").(string), "arn:aws:iam:") {
			d.Set("iam_role", *service.RoleArn)
		} else {
			roleARN := getNameFromARN(*service.RoleArn)
			d.Set("iam_role", roleARN)
		}
	}

	if service.DeploymentConfiguration != nil {
		d.Set("deployment_maximum_percent", *service.DeploymentConfiguration.MaximumPercent)
		d.Set("deployment_minimum_healthy_percent", *service.DeploymentConfiguration.MinimumHealthyPercent)
	}

	if service.LoadBalancers != nil {
		d.Set("load_balancers", flattenEcsLoadBalancers(service.LoadBalancers))
	}

	return nil
}

func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	log.Printf("[DEBUG] Updating ECS service %s", d.Id())
	input := ecs.UpdateServiceInput{
		Service: aws.String(d.Id()),
		Cluster: aws.String(d.Get("cluster").(string)),
	}

	if d.HasChange("desired_count") {
		_, n := d.GetChange("desired_count")
		input.DesiredCount = aws.Int64(int64(n.(int)))
	}
	if d.HasChange("task_definition") {
		_, n := d.GetChange("task_definition")
		input.TaskDefinition = aws.String(n.(string))
	}

	if d.HasChange("deployment_maximum_percent") || d.HasChange("deployment_minimum_healthy_percent") {
		input.DeploymentConfiguration = &ecs.DeploymentConfiguration{
			MaximumPercent:        aws.Int64(int64(d.Get("deployment_maximum_percent").(int))),
			MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))),
		}
	}

	out, err := conn.UpdateService(&input)
	if err != nil {
		return err
	}
	service := out.Service
	log.Printf("[DEBUG] Updated ECS service %s", service)

	return resourceAwsEcsServiceRead(d, meta)
}

func resourceAwsEcsServiceDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	// Check if it's not already gone
	resp, err := conn.DescribeServices(&ecs.DescribeServicesInput{
		Services: []*string{aws.String(d.Id())},
		Cluster:  aws.String(d.Get("cluster").(string)),
	})
	if err != nil {
		return err
	}

	if len(resp.Services) == 0 {
		log.Printf("[DEBUG] ECS Service %q is already gone", d.Id())
		return nil
	}

	log.Printf("[DEBUG] ECS service %s is currently %s", d.Id(), *resp.Services[0].Status)

	if *resp.Services[0].Status == "INACTIVE" {
		return nil
	}

	// Drain the ECS service
	if *resp.Services[0].Status != "DRAINING" {
		log.Printf("[DEBUG] Draining ECS service %s", d.Id())
		_, err = conn.UpdateService(&ecs.UpdateServiceInput{
			Service:      aws.String(d.Id()),
			Cluster:      aws.String(d.Get("cluster").(string)),
			DesiredCount: aws.Int64(int64(0)),
		})
		if err != nil {
			return err
		}
	}

	// Wait until the ECS service is drained
	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
		input := ecs.DeleteServiceInput{
			Service: aws.String(d.Id()),
			Cluster: aws.String(d.Get("cluster").(string)),
		}

		log.Printf("[DEBUG] Trying to delete ECS service %s", input)
		_, err := conn.DeleteService(&input)
		if err == nil {
			return nil
		}

		ec2err, ok := err.(awserr.Error)
		if !ok {
			return resource.NonRetryableError(err)
		}
		if ec2err.Code() == "InvalidParameterException" {
			// Prevent "The service cannot be stopped while deployments are active."
			log.Printf("[DEBUG] Trying to delete ECS service again: %q",
				ec2err.Message())
			return resource.RetryableError(err)
		}

		return resource.NonRetryableError(err)

	})
	if err != nil {
		return err
	}

	// Wait until it's deleted
	wait := resource.StateChangeConf{
		Pending:    []string{"DRAINING"},
		Target:     []string{"INACTIVE"},
		Timeout:    5 * time.Minute,
		MinTimeout: 1 * time.Second,
		Refresh: func() (interface{}, string, error) {
			log.Printf("[DEBUG] Checking if ECS service %s is INACTIVE", d.Id())
			resp, err := conn.DescribeServices(&ecs.DescribeServicesInput{
				Services: []*string{aws.String(d.Id())},
				Cluster:  aws.String(d.Get("cluster").(string)),
			})
			if err != nil {
				return resp, "FAILED", err
			}

			log.Printf("[DEBUG] ECS service (%s) is currently %q", d.Id(), *resp.Services[0].Status)
			return resp, *resp.Services[0].Status, nil
		},
	}

	_, err = wait.WaitForState()
	if err != nil {
		return err
	}

	log.Printf("[DEBUG] ECS service %s deleted.", d.Id())
	return nil
}

func resourceAwsEcsLoadBalancerHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["elb_name"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["container_name"].(string)))
	buf.WriteString(fmt.Sprintf("%d-", m["container_port"].(int)))

	return hashcode.String(buf.String())
}

func buildFamilyAndRevisionFromARN(arn string) string {
	return strings.Split(arn, "/")[1]
}

// Expects the following ARNs:
// arn:aws:iam::0123456789:role/EcsService
// arn:aws:ecs:us-west-2:0123456789:cluster/radek-cluster
func getNameFromARN(arn string) string {
	return strings.Split(arn, "/")[1]
}

func parseTaskDefinition(taskDefinition string) (string, string, error) {
	matches := taskDefinitionRE.FindAllStringSubmatch(taskDefinition, 2)

	if len(matches) == 0 || len(matches[0]) != 3 {
		return "", "", fmt.Errorf(
			"Invalid task definition format, family:rev or ARN expected (%#v)",
			taskDefinition)
	}

	return matches[0][1], matches[0][2], nil
}
<commit_msg>Validate number of load_balancers<commit_after>package aws

import (
	"bytes"
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"
)

var taskDefinitionRE = regexp.MustCompile("^([a-zA-Z0-9_-]+):([0-9]+)$")

func resourceAwsEcsService() *schema.Resource {
	return &schema.Resource{
		Create: resourceAwsEcsServiceCreate,
		Read:   resourceAwsEcsServiceRead,
		Update: resourceAwsEcsServiceUpdate,
		Delete: resourceAwsEcsServiceDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"cluster": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"task_definition": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"desired_count": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
			},

			"iam_role": &schema.Schema{
				Type:     schema.TypeString,
				ForceNew: true,
				Optional: true,
			},

			"deployment_maximum_percent": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  200,
			},

			"deployment_minimum_healthy_percent": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  100,
			},

			"load_balancer": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				ForceNew: true,
				MaxItems: 1,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"elb_name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},

						"container_name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
							ForceNew: true,
						},

						"container_port": &schema.Schema{
							Type:     schema.TypeInt,
							Required: true,
							ForceNew: true,
						},
					},
				},
				Set: resourceAwsEcsLoadBalancerHash,
			},
		},
	}
}

func resourceAwsEcsServiceCreate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	input := ecs.CreateServiceInput{
		ServiceName:    aws.String(d.Get("name").(string)),
		TaskDefinition: aws.String(d.Get("task_definition").(string)),
		DesiredCount:   aws.Int64(int64(d.Get("desired_count").(int))),
		ClientToken:    aws.String(resource.UniqueId()),
		DeploymentConfiguration: &ecs.DeploymentConfiguration{
			MaximumPercent:        aws.Int64(int64(d.Get("deployment_maximum_percent").(int))),
			MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))),
		},
	}

	if v, ok := d.GetOk("cluster"); ok {
		input.Cluster = aws.String(v.(string))
	}

	loadBalancers := expandEcsLoadBalancers(d.Get("load_balancer").(*schema.Set).List())
	if len(loadBalancers) > 0 {
		log.Printf("[DEBUG] Adding ECS load balancers: %s", loadBalancers)
		input.LoadBalancers = loadBalancers
	}
	if v, ok := d.GetOk("iam_role"); ok {
		input.Role = aws.String(v.(string))
	}

	log.Printf("[DEBUG] Creating ECS service: %s", input)

	// Retry due to AWS IAM policy eventual consistency
	// See https://github.com/hashicorp/terraform/issues/2869
	var out *ecs.CreateServiceOutput
	var err error
	err = resource.Retry(2*time.Minute, func() *resource.RetryError {
		out, err = conn.CreateService(&input)

		if err != nil {
			ec2err, ok := err.(awserr.Error)
			if !ok {
				return resource.NonRetryableError(err)
			}
			if ec2err.Code() == "InvalidParameterException" {
				log.Printf("[DEBUG] Trying to create ECS service again: %q",
					ec2err.Message())
				return resource.RetryableError(err)
			}

			return resource.NonRetryableError(err)
		}

		return nil
	})
	if err != nil {
		return err
	}

	service := *out.Service

	log.Printf("[DEBUG] ECS service created: %s", *service.ServiceArn)
	d.SetId(*service.ServiceArn)

	return resourceAwsEcsServiceUpdate(d, meta)
}

func resourceAwsEcsServiceRead(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	log.Printf("[DEBUG] Reading ECS service %s", d.Id())
	input := ecs.DescribeServicesInput{
		Services: []*string{aws.String(d.Id())},
		Cluster:  aws.String(d.Get("cluster").(string)),
	}

	out, err := conn.DescribeServices(&input)
	if err != nil {
		return err
	}

	if len(out.Services) < 1 {
		log.Printf("[DEBUG] Removing ECS service %s (%s) because it's gone", d.Get("name").(string), d.Id())
		d.SetId("")
		return nil
	}

	service := out.Services[0]

	// Status==INACTIVE means deleted service
	if *service.Status == "INACTIVE" {
		log.Printf("[DEBUG] Removing ECS service %q because it's INACTIVE", *service.ServiceArn)
		d.SetId("")
		return nil
	}

	log.Printf("[DEBUG] Received ECS service %s", service)

	d.SetId(*service.ServiceArn)
	d.Set("name", *service.ServiceName)

	// Save task definition in the same format
	if strings.HasPrefix(d.Get("task_definition").(string), "arn:aws:ecs:") {
		d.Set("task_definition", *service.TaskDefinition)
	} else {
		taskDefinition := buildFamilyAndRevisionFromARN(*service.TaskDefinition)
		d.Set("task_definition", taskDefinition)
	}

	d.Set("desired_count", *service.DesiredCount)

	// Save cluster in the same format
	if strings.HasPrefix(d.Get("cluster").(string), "arn:aws:ecs:") {
		d.Set("cluster", *service.ClusterArn)
	} else {
		clusterARN := getNameFromARN(*service.ClusterArn)
		d.Set("cluster", clusterARN)
	}

	// Save IAM role in the same format
	if service.RoleArn != nil {
		if strings.HasPrefix(d.Get("iam_role").(string), "arn:aws:iam:") {
			d.Set("iam_role", *service.RoleArn)
		} else {
			roleARN := getNameFromARN(*service.RoleArn)
			d.Set("iam_role", roleARN)
		}
	}

	if service.DeploymentConfiguration != nil {
		d.Set("deployment_maximum_percent", *service.DeploymentConfiguration.MaximumPercent)
		d.Set("deployment_minimum_healthy_percent", *service.DeploymentConfiguration.MinimumHealthyPercent)
	}

	if service.LoadBalancers != nil {
		d.Set("load_balancers", flattenEcsLoadBalancers(service.LoadBalancers))
	}

	return nil
}

func resourceAwsEcsServiceUpdate(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	log.Printf("[DEBUG] Updating ECS service %s", d.Id())
	input := ecs.UpdateServiceInput{
		Service: aws.String(d.Id()),
		Cluster: aws.String(d.Get("cluster").(string)),
	}

	if d.HasChange("desired_count") {
		_, n := d.GetChange("desired_count")
		input.DesiredCount = aws.Int64(int64(n.(int)))
	}
	if d.HasChange("task_definition") {
		_, n := d.GetChange("task_definition")
		input.TaskDefinition = aws.String(n.(string))
	}

	if d.HasChange("deployment_maximum_percent") || d.HasChange("deployment_minimum_healthy_percent") {
		input.DeploymentConfiguration = &ecs.DeploymentConfiguration{
			MaximumPercent:        aws.Int64(int64(d.Get("deployment_maximum_percent").(int))),
			MinimumHealthyPercent: aws.Int64(int64(d.Get("deployment_minimum_healthy_percent").(int))),
		}
	}

	out, err := conn.UpdateService(&input)
	if err != nil {
		return err
	}
	service := out.Service
	log.Printf("[DEBUG] Updated ECS service %s", service)

	return resourceAwsEcsServiceRead(d, meta)
}

func resourceAwsEcsServiceDelete(d *schema.ResourceData, meta interface{}) error {
	conn := meta.(*AWSClient).ecsconn

	// Check if it's not already gone
	resp, err := conn.DescribeServices(&ecs.DescribeServicesInput{
		Services: []*string{aws.String(d.Id())},
		Cluster:  aws.String(d.Get("cluster").(string)),
	})
	if err != nil {
		return err
	}

	if len(resp.Services) == 0 {
		log.Printf("[DEBUG] ECS Service %q is already gone", d.Id())
		return nil
	}

	log.Printf("[DEBUG] ECS service %s is currently %s", d.Id(), *resp.Services[0].Status)

	if *resp.Services[0].Status == "INACTIVE" {
		return nil
	}

	// Drain the ECS service
	if *resp.Services[0].Status != "DRAINING" {
		log.Printf("[DEBUG] Draining ECS service %s", d.Id())
		_, err = conn.UpdateService(&ecs.UpdateServiceInput{
			Service:      aws.String(d.Id()),
			Cluster:      aws.String(d.Get("cluster").(string)),
			DesiredCount: aws.Int64(int64(0)),
		})
		if err != nil {
			return err
		}
	}

	// Wait until the ECS service is drained
	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
		input := ecs.DeleteServiceInput{
			Service: aws.String(d.Id()),
			Cluster: aws.String(d.Get("cluster").(string)),
		}

		log.Printf("[DEBUG] Trying to delete ECS service %s", input)
		_, err := conn.DeleteService(&input)
		if err == nil {
			return nil
		}

		ec2err, ok := err.(awserr.Error)
		if !ok {
			return resource.NonRetryableError(err)
		}
		if ec2err.Code() == "InvalidParameterException" {
			// Prevent "The service cannot be stopped while deployments are active."
			log.Printf("[DEBUG] Trying to delete ECS service again: %q",
				ec2err.Message())
			return resource.RetryableError(err)
		}

		return resource.NonRetryableError(err)

	})
	if err != nil {
		return err
	}

	// Wait until it's deleted
	wait := resource.StateChangeConf{
		Pending:    []string{"DRAINING"},
		Target:     []string{"INACTIVE"},
		Timeout:    5 * time.Minute,
		MinTimeout: 1 * time.Second,
		Refresh: func() (interface{}, string, error) {
			log.Printf("[DEBUG] Checking if ECS service %s is INACTIVE", d.Id())
			resp, err := conn.DescribeServices(&ecs.DescribeServicesInput{
				Services: []*string{aws.String(d.Id())},
				Cluster:  aws.String(d.Get("cluster").(string)),
			})
			if err != nil {
				return resp, "FAILED", err
			}

			log.Printf("[DEBUG] ECS service (%s) is currently %q", d.Id(), *resp.Services[0].Status)
			return resp, *resp.Services[0].Status, nil
		},
	}

	_, err = wait.WaitForState()
	if err != nil {
		return err
	}

	log.Printf("[DEBUG] ECS service %s deleted.", d.Id())
	return nil
}

func resourceAwsEcsLoadBalancerHash(v interface{}) int {
	var buf bytes.Buffer
	m := v.(map[string]interface{})
	buf.WriteString(fmt.Sprintf("%s-", m["elb_name"].(string)))
	buf.WriteString(fmt.Sprintf("%s-", m["container_name"].(string)))
	buf.WriteString(fmt.Sprintf("%d-", m["container_port"].(int)))

	return hashcode.String(buf.String())
}

func buildFamilyAndRevisionFromARN(arn string) string {
	return strings.Split(arn, "/")[1]
}

// Expects the following ARNs:
// arn:aws:iam::0123456789:role/EcsService
// arn:aws:ecs:us-west-2:0123456789:cluster/radek-cluster
func getNameFromARN(arn string) string {
	return strings.Split(arn, "/")[1]
}

func parseTaskDefinition(taskDefinition string) (string, string, error) {
	matches := taskDefinitionRE.FindAllStringSubmatch(taskDefinition, 2)

	if len(matches) == 0 || len(matches[0]) != 3 {
		return "", "", fmt.Errorf(
			"Invalid task definition format, family:rev or ARN expected (%#v)",
			taskDefinition)
	}

	return matches[0][1], matches[0][2], nil
}
<|endoftext|>


<commit_before>package oauth2

import (
	"net/url"

	"github.com/asaskevich/govalidator"
	log "github.com/sirupsen/logrus"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const (
	collectionName string = "oauth_servers"
)

// Repository defines the behavior of a OAuth Server repo
type Repository interface {
	FindAll() ([]*OAuth, error)
	FindByName(name string) (*OAuth, error)
	FindByTokenURL(url url.URL) (*OAuth, error)
	Add(oauth *OAuth) error
	Save(oauth *OAuth) error
	Remove(id string) error
}

// MongoRepository represents a mongodb repository
type MongoRepository struct {
	session *mgo.Session
}

// NewMongoRepository creates a mongodb OAuth Server repo
func NewMongoRepository(session *mgo.Session) (*MongoRepository, error) {
	return &MongoRepository{session}, nil
}

// FindAll fetches all the OAuth Servers available
func (r *MongoRepository) FindAll() ([]*OAuth, error) {
	session, coll := r.getSession()
	defer session.Close()

	var result []*OAuth
	err := coll.Find(nil).All(&result)
	if err != nil {
		return nil, err
	}

	return result, nil
}

// FindByName find an OAuth Server by name
func (r *MongoRepository) FindByName(name string) (*OAuth, error) {
	session, coll := r.getSession()
	defer session.Close()

	var result *OAuth
	if err := coll.Find(bson.M{"name": name}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, ErrOauthServerNotFound
		}
		return nil, err
	}

	return result, nil
}

// Add add a new OAuth Server to the repository
func (r *MongoRepository) Add(oauth *OAuth) error {
	session, coll := r.getSession()
	defer session.Close()

	isValid, err := govalidator.ValidateStruct(oauth)
	if !isValid && err != nil {
		log.WithField("errors", err.Error()).Error("Validation errors")
		return err
	}

	if err = coll.Insert(oauth); err != nil {
		log.WithField("name", oauth.Name).
			WithError(err).
			Error("There was an error persisting the resource")
		if mgo.IsDup(err) {
			return ErrOauthServerNameExists
		}
		return err
	}

	log.WithField("name", oauth.Name).Debug("Resource persisted")
	return nil
}

// Save saves OAuth Server to the repository
func (r *MongoRepository) Save(oauth *OAuth) error {
	session, coll := r.getSession()
	defer session.Close()

	isValid, err := govalidator.ValidateStruct(oauth)
	if !isValid && err != nil {
		log.WithField("errors", err.Error()).Error("Validation errors")
		return err
	}

	_, err = coll.Upsert(bson.M{"name": oauth.Name}, oauth)
	if err != nil {
		log.WithField("name", oauth.Name).
			WithError(err).
			Error("There was an error adding the resource")
		return err
	}

	log.WithField("name", oauth.Name).Debug("Resource added")
	return nil
}

// Remove removes an OAuth Server from the repository
func (r *MongoRepository) Remove(name string) error {
	session, coll := r.getSession()
	defer session.Close()

	if err := coll.Remove(bson.M{"name": name}); err != nil {
		log.WithField("name", name).
			WithError(err).
			Error("There was an error removing the resource")
		return err
	}

	log.WithField("name", name).Debug("Resource removed")
	return nil
}

// FindByTokenURL returns OAuth Server records with corresponding token url
func (r *MongoRepository) FindByTokenURL(url url.URL) (*OAuth, error) {
	session, coll := r.getSession()
	defer session.Close()

	query := bson.M{
		"oauth_endpoints.token.upstreams.targets.target": url.String(),
	}

	var result *OAuth
	err := coll.Find(query).One(&result)

	return result, err
}

func (r *MongoRepository) getSession() (*mgo.Session, *mgo.Collection) {
	session := r.session.Copy()
	coll := session.DB("").C(collectionName)

	return session, coll
}
<commit_msg>Fixed a bug when rows were empty<commit_after>package oauth2

import (
	"net/url"

	"github.com/asaskevich/govalidator"
	log "github.com/sirupsen/logrus"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const (
	collectionName string = "oauth_servers"
)

// Repository defines the behavior of a OAuth Server repo
type Repository interface {
	FindAll() ([]*OAuth, error)
	FindByName(name string) (*OAuth, error)
	FindByTokenURL(url url.URL) (*OAuth, error)
	Add(oauth *OAuth) error
	Save(oauth *OAuth) error
	Remove(id string) error
}

// MongoRepository represents a mongodb repository
type MongoRepository struct {
	session *mgo.Session
}

// NewMongoRepository creates a mongodb OAuth Server repo
func NewMongoRepository(session *mgo.Session) (*MongoRepository, error) {
	return &MongoRepository{session}, nil
}

// FindAll fetches all the OAuth Servers available
func (r *MongoRepository) FindAll() ([]*OAuth, error) {
	session, coll := r.getSession()
	defer session.Close()

	result := []*OAuth{}
	err := coll.Find(nil).All(&result)
	if err != nil {
		return result, err
	}

	return result, nil
}

// FindByName find an OAuth Server by name
func (r *MongoRepository) FindByName(name string) (*OAuth, error) {
	session, coll := r.getSession()
	defer session.Close()

	var result *OAuth
	if err := coll.Find(bson.M{"name": name}).One(&result); err != nil {
		if err == mgo.ErrNotFound {
			return nil, ErrOauthServerNotFound
		}
		return nil, err
	}

	return result, nil
}

// Add add a new OAuth Server to the repository
func (r *MongoRepository) Add(oauth *OAuth) error {
	session, coll := r.getSession()
	defer session.Close()

	isValid, err := govalidator.ValidateStruct(oauth)
	if !isValid && err != nil {
		log.WithField("errors", err.Error()).Error("Validation errors")
		return err
	}

	if err = coll.Insert(oauth); err != nil {
		log.WithField("name", oauth.Name).
			WithError(err).
			Error("There was an error persisting the resource")
		if mgo.IsDup(err) {
			return ErrOauthServerNameExists
		}
		return err
	}

	log.WithField("name", oauth.Name).Debug("Resource persisted")
	return nil
}

// Save saves OAuth Server to the repository
func (r *MongoRepository) Save(oauth *OAuth) error {
	session, coll := r.getSession()
	defer session.Close()

	isValid, err := govalidator.ValidateStruct(oauth)
	if !isValid && err != nil {
		log.WithField("errors", err.Error()).Error("Validation errors")
		return err
	}

	_, err = coll.Upsert(bson.M{"name": oauth.Name}, oauth)
	if err != nil {
		log.WithField("name", oauth.Name).
			WithError(err).
			Error("There was an error adding the resource")
		return err
	}

	log.WithField("name", oauth.Name).Debug("Resource added")
	return nil
}

// Remove removes an OAuth Server from the repository
func (r *MongoRepository) Remove(name string) error {
	session, coll := r.getSession()
	defer session.Close()

	if err := coll.Remove(bson.M{"name": name}); err != nil {
		log.WithField("name", name).
			WithError(err).
			Error("There was an error removing the resource")
		return err
	}

	log.WithField("name", name).Debug("Resource removed")
	return nil
}

// FindByTokenURL returns OAuth Server records with corresponding token url
func (r *MongoRepository) FindByTokenURL(url url.URL) (*OAuth, error) {
	session, coll := r.getSession()
	defer session.Close()

	query := bson.M{
		"oauth_endpoints.token.upstreams.targets.target": url.String(),
	}

	var result *OAuth
	err := coll.Find(query).One(&result)

	return result, err
}

func (r *MongoRepository) getSession() (*mgo.Session, *mgo.Collection) {
	session := r.session.Copy()
	coll := session.DB("").C(collectionName)

	return session, coll
}
<|endoftext|>