func (s *etcdStore) Find(kind runtime.Kind, result interface{}, opts ...store.FindOpt) error {
	findOpts := store.NewFindOpts(opts)
	info := s.types.Get(kind)
	resultTypeElem := reflect.TypeOf(info.New())
	resultTypeSingle := reflect.PtrTo(reflect.TypeOf(info.New()))
	resultTypeList := reflect.PtrTo(reflect.SliceOf(resultTypeElem))
	resultList := false
	resultType := reflect.TypeOf(result)
	if resultType == resultTypeSingle {
	} else if resultType == resultTypeList {
		resultList = true
	} else {
		fmt.Printf("result should be %s or %s, but found: %s\n", resultTypeSingle, resultTypeList, resultType)
	}
	v := reflect.ValueOf(result).Elem()
	if findOpts.GetKeyPrefix() != "" {
		return s.findByKeyPrefix(findOpts, info, func(elem interface{}) {
			v.Set(reflect.Append(v, reflect.ValueOf(elem)))
		})
	} else if findOpts.GetKey() != "" && findOpts.GetFieldEqName() == "" {
		return s.findByKey(findOpts, info, func(elem interface{}) {
			if elem == nil {
				v.Set(reflect.Zero(v.Type()))
			} else {
				v.Set(reflect.ValueOf(elem))
			}
		})
	} else {
		return s.findByFieldEq(findOpts, info, func(elem interface{}) {
			if !resultList {
				if elem == nil {
					v.Set(reflect.Zero(v.Type()))
				} else {
					v.Set(reflect.ValueOf(elem))
				}
			} else {
				v.Set(reflect.Append(v, reflect.ValueOf(elem)))
			}
		})
	}
}
/*
Current Find use cases:

Find(kind, keyPrefix)
Find(kind, key, gen)  (gen=0 for non-versioned)
Find(kind, key, WithWhereEq)
Find(kind, key, WithWhereEq, WithGetFirst)
Find(kind, key, WithWhereEq, WithGetLast)

\\ summary: keyPrefix OR key+gen OR key + whereEq+list/first/last

Workflow:
validate parameters and result
identify requested list or one(first or last)
build list of keys that are result (could be just build key from parameters or use index)
based on requested list/first/last get corresponding element from the key list and query value for it
package etcd
import (
	"context"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"github.com/Aptomi/aptomi/pkg/runtime/store"
	etcd "github.com/coreos/etcd/clientv3"
	etcdconc "github.com/coreos/etcd/clientv3/concurrency"
	"github.com/coreos/etcd/clientv3/namespace"
)
type etcdStore struct {
	client *etcd.Client
	types  *runtime.Types
	codec  store.Codec
}
func New(cfg Config, types *runtime.Types, codec store.Codec) (store.Interface, error) {
	if len(cfg.Endpoints) == 0 {
		cfg.Endpoints = []string{"localhost:2379"}
	}
	client, err := etcd.New(etcd.Config{
		Endpoints:            cfg.Endpoints,
		DialTimeout:          dialTimeout,
		DialKeepAliveTime:    keepaliveTime,
		DialKeepAliveTimeout: keepaliveTimeout,
	})
	if err != nil {
		return nil, fmt.Errorf("error while connecting to etcd: %s", err)
	}
	cfg.Prefix = strings.Trim(cfg.Prefix, "/")
	if cfg.Prefix != "" {
		cfg.Prefix = "/" + cfg.Prefix
		client.KV = namespace.NewKV(client.KV, cfg.Prefix)
		client.Lease = namespace.NewLease(client.Lease, cfg.Prefix)
		client.Watcher = namespace.NewWatcher(client.Watcher, cfg.Prefix)
	}
	return &etcdStore{
		client: client,
		types:  types,
		codec:  codec,
	}, nil
}
func (s *etcdStore) Close() error {
	return s.client.Close()
}
func (s *etcdStore) Save(newStorable runtime.Storable, opts ...store.SaveOpt) (bool, error) {
	if newStorable == nil {
		return false, fmt.Errorf("can't save nil")
	}
	saveOpts := store.NewSaveOpts(opts)
	info := s.types.Get(newStorable.GetKind())
	indexes := store.IndexesFor(info)
	key := "/" + runtime.KeyForStorable(newStorable)
	if !info.Versioned {
		data := s.marshal(newStorable)
		_, err := s.client.KV.Put(context.TODO(), "/object"+key+"@"+runtime.LastOrEmptyGen.String(), string(data))
		return false, err
	}
	var newVersion bool
	newObj := newStorable.(runtime.Versioned)  
	_, err := etcdconc.NewSTM(s.client, func(stm etcdconc.STM) error {
		var prevObj runtime.Storable
		if saveOpts.IsReplaceOrForceGen() {
			newGen := newObj.GetGeneration()
			if newGen == runtime.LastOrEmptyGen {
				return fmt.Errorf("error while saving object %s with replaceOrForceGen option but with empty generation", key)
			}
			oldObjRaw := stm.Get("/object" + key + "@" + newGen.String())
			if oldObjRaw != "" {
				prevObj = info.New().(runtime.Storable)  
				s.unmarshal([]byte(oldObjRaw), prevObj)
			}
		} else {
			lastGenRaw := stm.Get("/index/" + indexes.NameForStorable(store.LastGenIndex, newStorable, s.codec))
			if lastGenRaw == "" {
				newObj.SetGeneration(runtime.FirstGen)
				newVersion = true
			} else {
				lastGen := s.unmarshalGen(lastGenRaw)
				oldObjRaw := stm.Get("/object" + key + "@" + lastGen.String())
				if oldObjRaw == "" {
					return fmt.Errorf("last gen index for %s seems to be corrupted: generation doesn't exist", key)
				}
				prevObj = info.New().(runtime.Storable)  
				s.unmarshal([]byte(oldObjRaw), prevObj)
				newObj.SetGeneration(lastGen)
				if reflect.DeepEqual(prevObj, newObj) {
					return nil
				}
				newObj.SetGeneration(lastGen.Next())
				newVersion = true
			}
		}
		data := s.marshal(newObj)
		newGen := newObj.GetGeneration()
		stm.Put("/object"+key+"@"+newGen.String(), string(data))
		if prevObj != nil && prevObj.(runtime.Versioned).GetGeneration() == newGen {
			for _, index := range indexes.List {
				indexName := index.NameForStorable(prevObj, s.codec)
				if indexName == "" {
					continue
				}
				indexKey := "/index/" + indexName
				if index.Type == store.IndexTypeListGen {
					s.updateIndex(stm, indexKey, prevObj.(runtime.Versioned).GetGeneration(), true)
				}
			}
		}
		for _, index := range indexes.List {
			indexName := index.NameForStorable(newStorable, s.codec)
			if indexName == "" {
				continue
			}
			indexKey := "/index/" + indexName
			if index.Type == store.IndexTypeLastGen {
				stm.Put(indexKey, s.marshalGen(newGen))
			} else if index.Type == store.IndexTypeListGen {
				s.updateIndex(stm, indexKey, newGen, false)
			} else {
				panic("only indexes with types store.IndexTypeLastGen and store.IndexTypeListGen are currently supported by Etcd store")
			}
		}
		return nil
	})
	return newVersion, err
}
func (s *etcdStore) updateIndex(stm etcdconc.STM, indexKey string, newGen runtime.Generation, delete bool) {
	valueList := &store.IndexValueList{}
	valueListRaw := stm.Get(indexKey)
	if valueListRaw != "" {
		s.unmarshal([]byte(valueListRaw), valueList)
	}
	gen := []byte(s.marshalGen(newGen))
	if delete {
		valueList.Remove(gen)
	} else {
		valueList.Add(gen)
	}
	data := s.marshal(valueList)
	stm.Put(indexKey, string(data))
}