func (c *Client) DiscoverOne(ctx context.Context, serviceName string) (*stela.Service, error) {
	resp, err := c.rpc.DiscoverOne(ctx, &pb.DiscoverRequest{ServiceName: serviceName})
	if err != nil {
		return nil, err
	}
	return &stela.Service{
		Name:     resp.Name,
		Hostname: resp.Hostname,
		IPv4:     resp.IPv4,
		IPv6:     resp.IPv6,
		Port:     resp.Port,
		Priority: resp.Priority,
		Value:    resp.Value,
	}, nil
}
DiscoverOne finds a single instance of a service based on name.
package api
import (
	"crypto/tls"
	"crypto/x509"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"sync"
	"github.com/forestgiant/netutil"
	"github.com/forestgiant/stela"
	"github.com/forestgiant/stela/pb"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	fggrpclog "github.com/forestgiant/grpclog"
)
func init() {
	grpclog.SetLogger(&fggrpclog.Suppressed{})
}
type Client struct {
	stela.Client
	mu sync.RWMutex
	rpc       pb.StelaClient
	conn      *grpc.ClientConn
	Hostname  string
	callbacks map[string]func(s *stela.Service)
}
func NewClient(ctx context.Context, stelaAddress string, opts []grpc.DialOption) (*Client, error) {
	c := &Client{}
	host, err := os.Hostname()
	if err != nil {
		return nil, err
	}
	c.Hostname = host
	c.Address = netutil.LocalIPv4().String()
	if len(opts) == 0 {
		opts = append(opts, grpc.WithInsecure())
	}
	opts = append(opts, grpc.FailOnNonTempDialError(true))
	opts = append(opts, grpc.WithBlock())
	c.conn, err = grpc.DialContext(ctx, stelaAddress, opts...)
	if err != nil {
		return nil, err
	}
	c.rpc = pb.NewStelaClient(c.conn)
	resp, err := c.rpc.AddClient(ctx, &pb.AddClientRequest{ClientAddress: c.Address})
	if err != nil {
		return nil, err
	}
	c.ID = resp.ClientId
	if err := c.connect(); err != nil {
		return nil, err
	}
	return c, nil
}
func NewTLSClient(ctx context.Context, serverAddress string, serverName string, cert string, privateKey string, certificateAuthority string) (*Client, error) {
	var opts []grpc.DialOption
	if len(cert) == 0 || len(privateKey) == 0 || len(certificateAuthority) == 0 || len(serverName) == 0 {
		return nil, errors.New("Insufficient security credentials provided")
	}
	certificate, err := tls.LoadX509KeyPair(cert, privateKey)
	if err != nil {
		return nil, fmt.Errorf("Could not load client key pair: %s", err)
	}
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile(certificateAuthority)
	if err != nil {
		return nil, fmt.Errorf("Could not read ca certificate: %s", err)
	}
	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		return nil, errors.New("Failed to append ca certs")
	}
	creds := credentials.NewTLS(&tls.Config{
		ServerName:   serverName,
		Certificates: []tls.Certificate{certificate},
		RootCAs:      certPool,
	})
	opts = append(opts, grpc.WithTransportCredentials(creds))
	return NewClient(ctx, serverAddress, opts)
}
func (c *Client) init() {
	c.mu.Lock()
	if c.callbacks == nil {
		c.callbacks = make(map[string]func(s *stela.Service))
	}
	c.mu.Unlock()
}
func (c *Client) connect() error {
	ctx, cancelFunc := context.WithCancel(context.Background())
	c.mu.RLock()
	stream, err := c.rpc.Connect(ctx, &pb.ConnectRequest{ClientId: c.ID})
	c.mu.RUnlock()
	if err != nil {
		return err
	}
	go func() {
		defer c.conn.Close()
		defer cancelFunc()
		for {
			rs, err := stream.Recv()
			if err == io.EOF {
				break
			}
			if err != nil {
				return
			}
			c.mu.RLock()
			if c.callbacks != nil {
				c.callbacks[rs.Name](&stela.Service{
					Name:     rs.Name,
					Hostname: rs.Hostname,
					IPv4:     rs.IPv4,
					IPv6:     rs.IPv6,
					Port:     rs.Port,
					Priority: rs.Priority,
					Action:   rs.Action,
					Value:    rs.Value,
				})
			}
			c.mu.RUnlock()
		}
	}()
	return nil
}
func (c *Client) Subscribe(ctx context.Context, serviceName string, callback func(s *stela.Service)) error {
	c.mu.RLock()
	_, err := c.rpc.Subscribe(ctx,
		&pb.SubscribeRequest{
			ClientId:    c.ID,
			ServiceName: serviceName,
		})
	c.mu.RUnlock()
	if err != nil {
		return err
	}
	c.init()
	c.mu.Lock()
	c.callbacks[serviceName] = callback
	c.mu.Unlock()
	return nil
}
func (c *Client) Unsubscribe(ctx context.Context, serviceName string) error {
	c.mu.RLock()
	if c.callbacks == nil {
		c.mu.RUnlock()
		return errors.New("Client hasn't subscribed")
	}
	c.mu.RUnlock()
	c.mu.RLock()
	_, err := c.rpc.Unsubscribe(ctx,
		&pb.SubscribeRequest{
			ClientId:    c.ID,
			ServiceName: serviceName,
		})
	c.mu.RUnlock()
	if err != nil {
		return err
	}
	c.mu.Lock()
	delete(c.callbacks, serviceName)
	c.mu.Unlock()
	return nil
}
func (c *Client) Register(ctx context.Context, s *stela.Service) error {
	c.mu.RLock()
	defer c.mu.RUnlock()
	s.Hostname = c.Hostname
	if s.IPv4 == "" {
		s.IPv4 = c.Address
	}
	_, err := c.rpc.Register(ctx,
		&pb.RegisterRequest{
			ClientId: c.ID,
			Service: &pb.ServiceMessage{
				Name:     s.Name,
				Hostname: s.Hostname,
				IPv4:     s.IPv4,
				IPv6:     s.IPv6,
				Port:     s.Port,
				Priority: s.Priority,
				Value:    s.Value,
			},
		})
	if err != nil {
		return err
	}
	return nil
}
func (c *Client) Deregister(ctx context.Context, s *stela.Service) error {
	c.mu.RLock()
	defer c.mu.RUnlock()
	s.Hostname = c.Hostname
	if s.IPv4 == "" {
		s.IPv4 = c.Address
	}
	_, err := c.rpc.Deregister(ctx,
		&pb.RegisterRequest{
			ClientId: c.ID,
			Service: &pb.ServiceMessage{
				Name:     s.Name,
				Hostname: s.Hostname,
				IPv4:     s.IPv4,
				IPv6:     s.IPv6,
				Port:     s.Port,
				Priority: s.Priority,
			},
		})
	if err != nil {
		return err
	}
	return nil
}
func (c *Client) Discover(ctx context.Context, serviceName string) ([]*stela.Service, error) {
	resp, err := c.rpc.Discover(ctx, &pb.DiscoverRequest{ServiceName: serviceName})
	if err != nil {
		return nil, err
	}
	var services []*stela.Service
	for _, ds := range resp.Services {
		s := &stela.Service{
			Name:     ds.Name,
			Hostname: ds.Hostname,
			IPv4:     ds.IPv4,
			IPv6:     ds.IPv6,
			Port:     ds.Port,
			Priority: ds.Priority,
			Value:    ds.Value,
		}
		services = append(services, s)
	}
	return services, nil
}
func (c *Client) DiscoverRegex(ctx context.Context, reg string) ([]*stela.Service, error) {
	resp, err := c.rpc.DiscoverRegex(ctx, &pb.DiscoverRequest{ServiceName: reg})
	if err != nil {
		return nil, err
	}
	var services []*stela.Service
	for _, ds := range resp.Services {
		s := &stela.Service{
			Name:     ds.Name,
			Hostname: ds.Hostname,
			IPv4:     ds.IPv4,
			IPv6:     ds.IPv6,
			Port:     ds.Port,
			Priority: ds.Priority,
			Value:    ds.Value,
		}
		services = append(services, s)
	}
	return services, nil
}