func (c *Client) EntityKeys(ctx context.Context, id, action, ktype, key string) (map[string][]string, error) {
	if strings.ToUpper(action) != "READ" {
		if err := c.makeWritable(); err != nil {
			return nil, err
		}
	}
	ctx = c.appendMetadata(ctx)
	action = strings.ToUpper(action)
	a, ok := rpc.Action_value[action]
	if !ok {
		return nil, errors.New("action must be one of ADD, DROP, READ")
	}
	r := rpc.KVRequest{
		Target: &id,
		Action: rpc.Action(a).Enum(),
		Key:    &ktype,
		Value:  &key,
	}
	res, err := c.rpc.EntityKeys(ctx, &r)
	if err != nil {
		return nil, err
	}
	if action == "READ" {
		kv := parseKV(res.GetStrings())
		if ktype != "*" {
			return map[string][]string{ktype: kv[ktype]}, nil
		}
		return kv, nil
	}
	return nil, nil
}
EntityKeys handles updates to public keys stored on an entity.
These keys are public and can be queried without authentication.
The idea is to provide a means of distributing public keys for SSH
and PGP.
package netauth
import (
	"context"
	"errors"
	"sort"
	"strings"
	"google.golang.org/protobuf/proto"
	pb "github.com/netauth/protocol"
	rpc "github.com/netauth/protocol/v2"
)
func (c *Client) EntityCreate(ctx context.Context, id, secret string, number int) error {
	if err := c.makeWritable(); err != nil {
		return err
	}
	ctx = c.appendMetadata(ctx)
	r := rpc.EntityRequest{
		Entity: &pb.Entity{
			ID:     &id,
			Secret: &secret,
			Number: proto.Int32(int32(number)),
		},
	}
	_, err := c.rpc.EntityCreate(ctx, &r)
	return err
}
func (c *Client) EntityUpdate(ctx context.Context, id string, meta *pb.EntityMeta) error {
	if err := c.makeWritable(); err != nil {
		return err
	}
	ctx = c.appendMetadata(ctx)
	r := rpc.EntityRequest{
		Data: &pb.Entity{
			ID:   &id,
			Meta: meta,
		},
	}
	_, err := c.rpc.EntityUpdate(ctx, &r)
	return err
}
func (c *Client) EntityInfo(ctx context.Context, id string) (pb.Entity, error) {
	ctx = c.appendMetadata(ctx)
	r := rpc.EntityRequest{
		Entity: &pb.Entity{
			ID: &id,
		},
	}
	res, err := c.rpc.EntityInfo(ctx, &r)
	if err != nil {
		return pb.Entity{}, err
	}
	return *res.GetEntities()[0], nil
}
func (c *Client) EntitySearch(ctx context.Context, expr string) ([]*pb.Entity, error) {
	ctx = c.appendMetadata(ctx)
	r := rpc.SearchRequest{
		Expression: &expr,
	}
	res, err := c.rpc.EntitySearch(ctx, &r)
	return res.GetEntities(), err
}
func (c *Client) EntityUM(ctx context.Context, target, action, key, value string) (map[string][]string, error) {
	if strings.ToUpper(action) != "READ" {
		if err := c.makeWritable(); err != nil {
			return nil, err
		}
	}
	ctx = c.appendMetadata(ctx)
	action = strings.ToUpper(action)
	a, ok := rpc.Action_value[action]
	if !ok {
		return nil, errors.New("action must be one of UPSERT, CLEARFUZZY, CLEAREXACT, READ")
	}
	r := rpc.KVRequest{
		Target: &target,
		Key:    &key,
		Value:  &value,
		Action: rpc.Action(a).Enum(),
	}
	res, err := c.rpc.EntityUM(ctx, &r)
	if err != nil {
		return nil, err
	}
	if action == "READ" {
		kv := parseKV(res.GetStrings())
		if key != "*" {
			return map[string][]string{key: kv[key]}, nil
		}
		return kv, nil
	}
	return nil, nil
}
func (c *Client) EntityKVGet(ctx context.Context, id, key string) (map[string][]string, error) {
	ctx = c.appendMetadata(ctx)
	r := rpc.KV2Request{
		Target: &id,
		Data: &pb.KVData{
			Key: &key,
		},
	}
	res, err := c.rpc.EntityKVGet(ctx, &r)
	if err != nil {
		return nil, err
	}
	out := make(map[string][]string, len(res.GetKVData()))
	for _, kvd := range res.GetKVData() {
		sort.Slice(kvd.Values, func(i, j int) bool {
			return kvd.Values[i].GetIndex() < kvd.Values[j].GetIndex()
		})
		for _, v := range kvd.GetValues() {
			out[kvd.GetKey()] = append(out[kvd.GetKey()], v.GetValue())
		}
	}
	return out, nil
}
func (c *Client) EntityKVAdd(ctx context.Context, id, key string, values []string) error {
	if err := c.makeWritable(); err != nil {
		return err
	}
	ctx = c.appendMetadata(ctx)
	r := rpc.KV2Request{
		Target: &id,
		Data: &pb.KVData{
			Key: &key,
		},
	}
	v := make([]*pb.KVValue, len(values))
	for i := range values {
		v[i] = &pb.KVValue{
			Value: &values[i],
			Index: proto.Int32(int32(i)),
		}
	}
	r.Data.Values = v
	_, err := c.rpc.EntityKVAdd(ctx, &r)
	return err
}
func (c *Client) EntityKVDel(ctx context.Context, id, key string) error {
	if err := c.makeWritable(); err != nil {
		return err
	}
	ctx = c.appendMetadata(ctx)
	r := rpc.KV2Request{
		Target: &id,
		Data: &pb.KVData{
			Key: &key,
		},
	}
	_, err := c.rpc.EntityKVDel(ctx, &r)
	return err
}
func (c *Client) EntityKVReplace(ctx context.Context, id, key string, values []string) error {
	if err := c.makeWritable(); err != nil {
		return err
	}
	ctx = c.appendMetadata(ctx)
	r := rpc.KV2Request{
		Target: &id,
		Data: &pb.KVData{
			Key: &key,
		},
	}
	v := make([]*pb.KVValue, len(values))
	for i := range values {
		v[i] = &pb.KVValue{
			Value: &values[i],
			Index: proto.Int32(int32(i)),
		}
	}
	r.Data.Values = v
	_, err := c.rpc.EntityKVReplace(ctx, &r)
	return err
}