<commit_before>package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	log "github.com/Sirupsen/logrus"
	types "github.com/gogo/protobuf/types"

	"github.com/pachyderm/pachyderm/src/client/health"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pps"
)

// PfsAPIClient is an alias for pfs.APIClient.
type PfsAPIClient pfs.APIClient

// PpsAPIClient is an alias for pps.APIClient.
type PpsAPIClient pps.APIClient

// ObjectAPIClient is an alias for pfs.ObjectAPIClient
type ObjectAPIClient pfs.ObjectAPIClient

// An APIClient is a wrapper around pfs, pps and block APIClients.
type APIClient struct {
	PfsAPIClient
	PpsAPIClient
	ObjectAPIClient

	// addr is a "host:port" string pointing at a pachd endpoint
	addr string

	// context.Context includes context options for all RPCs, including deadline
	_ctx context.Context

	// clientConn is a cached grpc connection to 'addr'
	clientConn *grpc.ClientConn

	// healthClient is a cached healthcheck client connected to 'addr'
	healthClient health.HealthClient

	// cancel is the cancel function for 'clientConn' and is called if
	// 'healthClient' fails health checking
	cancel func()

	// streamSemaphore limits the number of concurrent message streams between
	// this client and pachd
	streamSemaphore chan struct{}

	// metricsUserID is an identifier that is included in usage metrics sent to
	// Pachyderm Inc. and is used to count the number of unique Pachyderm users.
	// If unset, no usage metrics are sent back to Pachyderm Inc.
	metricsUserID string

	// metricsPrefix is used to send information from this client to Pachyderm Inc
	// for usage metrics
	metricsPrefix string
}

// GetAddress returns the pachd host:post with which 'c' is communicating. If
// 'c' was created using NewInCluster or NewOnUserMachine then this is how the
// address may be retrieved from the environment.
func (c *APIClient) GetAddress() string {
	return c.addr
}

// DefaultMaxConcurrentStreams defines the max number of Putfiles or Getfiles happening simultaneously
const DefaultMaxConcurrentStreams uint = 100

// NewFromAddressWithConcurrency constructs a new APIClient and sets the max
// concurrency of streaming requests (GetFile / PutFile)
func NewFromAddressWithConcurrency(addr string, maxConcurrentStreams uint) (*APIClient, error) {
	c := &APIClient{
		addr:            addr,
		streamSemaphore: make(chan struct{}, maxConcurrentStreams),
	}
	if err := c.connect(); err != nil {
		return nil, err
	}
	return c, nil
}

// NewFromAddress constructs a new APIClient for the server at addr.
func NewFromAddress(addr string) (*APIClient, error) {
	return NewFromAddressWithConcurrency(addr, DefaultMaxConcurrentStreams)
}

// GetAddressFromUserMachine interprets the Pachyderm config in 'cfg' in the
// context of local environment variables and returns a "host:port" string
// pointing at a Pachd target.
func GetAddressFromUserMachine(cfg *config.Config) string {
	address := "0.0.0.0:30650"
	if cfg != nil && cfg.V1 != nil && cfg.V1.PachdAddress != "" {
		address = cfg.V1.PachdAddress
	}
	// ADDRESS environment variable (shell-local) overrides global config
	if envAddr := os.Getenv("ADDRESS"); envAddr != "" {
		address = envAddr
	}
	return address
}

// NewOnUserMachine constructs a new APIClient using env vars that may be set
// on a user's machine (i.e. ADDRESS), as well as $HOME/.pachyderm/config if it
// exists. This is primarily intended to be used with the pachctl binary, but
// may also be useful in tests.
//
// TODO(msteffen) this logic is fairly linux/unix specific, and makes pachctl
// incompatible with Windows. We may want to move this (and similar) logic into
// src/server and have it call a NewFromOptions() constructor.
func NewOnUserMachine(reportMetrics bool, prefix string) (*APIClient, error) {
	return NewOnUserMachineWithConcurrency(reportMetrics, prefix, DefaultMaxConcurrentStreams)
}

// NewOnUserMachineWithConcurrency is identical to NewOnUserMachine, but
// explicitly sets a limit on the number of RPC streams that may be open
// simultaneously
func NewOnUserMachineWithConcurrency(reportMetrics bool, prefix string, maxConcurrentStreams uint) (*APIClient, error) {
	cfg, err := config.Read()
	if err != nil {
		// metrics errors are non fatal
		log.Warningf("error loading user config from ~/.pachderm/config: %v", err)
	}

	// create new pachctl client
	client, err := NewFromAddress(GetAddressFromUserMachine(cfg))
	if err != nil {
		return nil, err
	}

	// Add metrics info
	if cfg != nil && cfg.UserID != "" && reportMetrics {
		client.metricsUserID = cfg.UserID
	}
	return client, nil
}

// NewInCluster constructs a new APIClient using env vars that Kubernetes creates.
// This should be used to access Pachyderm from within a Kubernetes cluster
// with Pachyderm running on it.
func NewInCluster() (*APIClient, error) {
	if addr := os.Getenv("PACHD_PORT_650_TCP_ADDR"); addr != "" {
		return NewFromAddress(fmt.Sprintf("%v:650", addr))
	}
	return nil, fmt.Errorf("PACHD_PORT_650_TCP_ADDR not set")
}

// Close the connection to gRPC
func (c *APIClient) Close() error {
	return c.clientConn.Close()
}

// KeepConnected periodically health checks the connection and attempts to
// reconnect if it becomes unhealthy.
func (c *APIClient) KeepConnected(cancel chan bool) {
	for {
		select {
		case <-cancel:
			return
		case <-time.After(time.Second * 5):
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
			if _, err := c.healthClient.Health(ctx, &types.Empty{}); err != nil {
				c.cancel()
				c.connect()
			}
			cancel()
		}
	}
}

// DeleteAll deletes everything in the cluster.
// Use with caution, there is no undo.
func (c APIClient) DeleteAll() error {
	if _, err := c.PpsAPIClient.DeleteAll(
		c.ctx(),
		&types.Empty{},
	); err != nil {
		return sanitizeErr(err)
	}
	if _, err := c.PfsAPIClient.DeleteAll(
		c.ctx(),
		&types.Empty{},
	); err != nil {
		return sanitizeErr(err)
	}
	return nil
}

// SetMaxConcurrentStreams Sets the maximum number of concurrent streams the
// client can have. It is not safe to call this operations while operations are
// outstanding.
func (c APIClient) SetMaxConcurrentStreams(n int) {
	c.streamSemaphore = make(chan struct{}, n)
}

// EtcdDialOptions is a helper returning a slice of grpc.Dial options
// such that grpc.Dial() is synchronous: the call doesn't return until
// the connection has been established and it's safe to send RPCs
func EtcdDialOptions() []grpc.DialOption {
	return []grpc.DialOption{
		// Don't return from Dial() until the connection has been established
		grpc.WithBlock(),

		// If no connection is established in 10s, fail the call
		grpc.WithTimeout(10 * time.Second),
	}
}

// PachDialOptions is a helper returning a slice of grpc.Dial options
// such that
// - TLS is disabled
// - Dial is synchronous: the call doesn't return until the connection has been
//                        established and it's safe to send RPCs
//
// This is primarily useful for Pachd and Worker clients
func PachDialOptions() []grpc.DialOption {
	return append(EtcdDialOptions(), grpc.WithInsecure())
}

func (c *APIClient) connect() error {
	clientConn, err := grpc.Dial(c.addr, PachDialOptions()...)
	if err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(context.Background())
	c.PfsAPIClient = pfs.NewAPIClient(clientConn)
	c.PpsAPIClient = pps.NewAPIClient(clientConn)
	c.ObjectAPIClient = pfs.NewObjectAPIClient(clientConn)
	c.clientConn = clientConn
	c.healthClient = health.NewHealthClient(clientConn)
	c._ctx = ctx
	c.cancel = cancel
	return nil
}

func (c *APIClient) addMetadata(ctx context.Context) context.Context {
	if c.metricsUserID == "" {
		return ctx
	}
	// metadata API downcases all the key names
	return metadata.NewContext(
		ctx,
		metadata.Pairs(
			"userid", c.metricsUserID,
			"prefix", c.metricsPrefix,
		),
	)
}

// TODO this method only exists because we initialize some APIClient in such a
// way that ctx will be nil
func (c *APIClient) ctx() context.Context {
	if c._ctx == nil {
		return c.addMetadata(context.Background())
	}
	return c.addMetadata(c._ctx)
}

func sanitizeErr(err error) error {
	if err == nil {
		return nil
	}

	return errors.New(grpc.ErrorDesc(err))
}
<commit_msg>Update comment slightly<commit_after>package client

import (
	"errors"
	"fmt"
	"os"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	log "github.com/Sirupsen/logrus"
	types "github.com/gogo/protobuf/types"

	"github.com/pachyderm/pachyderm/src/client/health"
	"github.com/pachyderm/pachyderm/src/client/pfs"
	"github.com/pachyderm/pachyderm/src/client/pkg/config"
	"github.com/pachyderm/pachyderm/src/client/pps"
)

// PfsAPIClient is an alias for pfs.APIClient.
type PfsAPIClient pfs.APIClient

// PpsAPIClient is an alias for pps.APIClient.
type PpsAPIClient pps.APIClient

// ObjectAPIClient is an alias for pfs.ObjectAPIClient
type ObjectAPIClient pfs.ObjectAPIClient

// An APIClient is a wrapper around pfs, pps and block APIClients.
type APIClient struct {
	PfsAPIClient
	PpsAPIClient
	ObjectAPIClient

	// addr is a "host:port" string pointing at a pachd endpoint
	addr string

	// context.Context includes context options for all RPCs, including deadline
	_ctx context.Context

	// clientConn is a cached grpc connection to 'addr'
	clientConn *grpc.ClientConn

	// healthClient is a cached healthcheck client connected to 'addr'
	healthClient health.HealthClient

	// cancel is the cancel function for 'clientConn' and is called if
	// 'healthClient' fails health checking
	cancel func()

	// streamSemaphore limits the number of concurrent message streams between
	// this client and pachd
	streamSemaphore chan struct{}

	// metricsUserID is an identifier that is included in usage metrics sent to
	// Pachyderm Inc. and is used to count the number of unique Pachyderm users.
	// If unset, no usage metrics are sent back to Pachyderm Inc.
	metricsUserID string

	// metricsPrefix is used to send information from this client to Pachyderm Inc
	// for usage metrics
	metricsPrefix string
}

// GetAddress returns the pachd host:post with which 'c' is communicating. If
// 'c' was created using NewInCluster or NewOnUserMachine then this is how the
// address may be retrieved from the environment.
func (c *APIClient) GetAddress() string {
	return c.addr
}

// DefaultMaxConcurrentStreams defines the max number of Putfiles or Getfiles happening simultaneously
const DefaultMaxConcurrentStreams uint = 100

// NewFromAddressWithConcurrency constructs a new APIClient and sets the max
// concurrency of streaming requests (GetFile / PutFile)
func NewFromAddressWithConcurrency(addr string, maxConcurrentStreams uint) (*APIClient, error) {
	c := &APIClient{
		addr:            addr,
		streamSemaphore: make(chan struct{}, maxConcurrentStreams),
	}
	if err := c.connect(); err != nil {
		return nil, err
	}
	return c, nil
}

// NewFromAddress constructs a new APIClient for the server at addr.
func NewFromAddress(addr string) (*APIClient, error) {
	return NewFromAddressWithConcurrency(addr, DefaultMaxConcurrentStreams)
}

// GetAddressFromUserMachine interprets the Pachyderm config in 'cfg' in the
// context of local environment variables and returns a "host:port" string
// pointing at a Pachd target.
func GetAddressFromUserMachine(cfg *config.Config) string {
	address := "0.0.0.0:30650"
	if cfg != nil && cfg.V1 != nil && cfg.V1.PachdAddress != "" {
		address = cfg.V1.PachdAddress
	}
	// ADDRESS environment variable (shell-local) overrides global config
	if envAddr := os.Getenv("ADDRESS"); envAddr != "" {
		address = envAddr
	}
	return address
}

// NewOnUserMachine constructs a new APIClient using env vars that may be set
// on a user's machine (i.e. ADDRESS), as well as $HOME/.pachyderm/config if it
// exists. This is primarily intended to be used with the pachctl binary, but
// may also be useful in tests.
//
// TODO(msteffen) this logic is fairly linux/unix specific, and makes the
// pachyderm client library incompatible with Windows. We may want to move this
// (and similar) logic into src/server and have it call a NewFromOptions()
// constructor.
func NewOnUserMachine(reportMetrics bool, prefix string) (*APIClient, error) {
	return NewOnUserMachineWithConcurrency(reportMetrics, prefix, DefaultMaxConcurrentStreams)
}

// NewOnUserMachineWithConcurrency is identical to NewOnUserMachine, but
// explicitly sets a limit on the number of RPC streams that may be open
// simultaneously
func NewOnUserMachineWithConcurrency(reportMetrics bool, prefix string, maxConcurrentStreams uint) (*APIClient, error) {
	cfg, err := config.Read()
	if err != nil {
		// metrics errors are non fatal
		log.Warningf("error loading user config from ~/.pachderm/config: %v", err)
	}

	// create new pachctl client
	client, err := NewFromAddress(GetAddressFromUserMachine(cfg))
	if err != nil {
		return nil, err
	}

	// Add metrics info
	if cfg != nil && cfg.UserID != "" && reportMetrics {
		client.metricsUserID = cfg.UserID
	}
	return client, nil
}

// NewInCluster constructs a new APIClient using env vars that Kubernetes creates.
// This should be used to access Pachyderm from within a Kubernetes cluster
// with Pachyderm running on it.
func NewInCluster() (*APIClient, error) {
	if addr := os.Getenv("PACHD_PORT_650_TCP_ADDR"); addr != "" {
		return NewFromAddress(fmt.Sprintf("%v:650", addr))
	}
	return nil, fmt.Errorf("PACHD_PORT_650_TCP_ADDR not set")
}

// Close the connection to gRPC
func (c *APIClient) Close() error {
	return c.clientConn.Close()
}

// KeepConnected periodically health checks the connection and attempts to
// reconnect if it becomes unhealthy.
func (c *APIClient) KeepConnected(cancel chan bool) {
	for {
		select {
		case <-cancel:
			return
		case <-time.After(time.Second * 5):
			ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
			if _, err := c.healthClient.Health(ctx, &types.Empty{}); err != nil {
				c.cancel()
				c.connect()
			}
			cancel()
		}
	}
}

// DeleteAll deletes everything in the cluster.
// Use with caution, there is no undo.
func (c APIClient) DeleteAll() error {
	if _, err := c.PpsAPIClient.DeleteAll(
		c.ctx(),
		&types.Empty{},
	); err != nil {
		return sanitizeErr(err)
	}
	if _, err := c.PfsAPIClient.DeleteAll(
		c.ctx(),
		&types.Empty{},
	); err != nil {
		return sanitizeErr(err)
	}
	return nil
}

// SetMaxConcurrentStreams Sets the maximum number of concurrent streams the
// client can have. It is not safe to call this operations while operations are
// outstanding.
func (c APIClient) SetMaxConcurrentStreams(n int) {
	c.streamSemaphore = make(chan struct{}, n)
}

// EtcdDialOptions is a helper returning a slice of grpc.Dial options
// such that grpc.Dial() is synchronous: the call doesn't return until
// the connection has been established and it's safe to send RPCs
func EtcdDialOptions() []grpc.DialOption {
	return []grpc.DialOption{
		// Don't return from Dial() until the connection has been established
		grpc.WithBlock(),

		// If no connection is established in 10s, fail the call
		grpc.WithTimeout(10 * time.Second),
	}
}

// PachDialOptions is a helper returning a slice of grpc.Dial options
// such that
// - TLS is disabled
// - Dial is synchronous: the call doesn't return until the connection has been
//                        established and it's safe to send RPCs
//
// This is primarily useful for Pachd and Worker clients
func PachDialOptions() []grpc.DialOption {
	return append(EtcdDialOptions(), grpc.WithInsecure())
}

func (c *APIClient) connect() error {
	clientConn, err := grpc.Dial(c.addr, PachDialOptions()...)
	if err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(context.Background())
	c.PfsAPIClient = pfs.NewAPIClient(clientConn)
	c.PpsAPIClient = pps.NewAPIClient(clientConn)
	c.ObjectAPIClient = pfs.NewObjectAPIClient(clientConn)
	c.clientConn = clientConn
	c.healthClient = health.NewHealthClient(clientConn)
	c._ctx = ctx
	c.cancel = cancel
	return nil
}

func (c *APIClient) addMetadata(ctx context.Context) context.Context {
	if c.metricsUserID == "" {
		return ctx
	}
	// metadata API downcases all the key names
	return metadata.NewContext(
		ctx,
		metadata.Pairs(
			"userid", c.metricsUserID,
			"prefix", c.metricsPrefix,
		),
	)
}

// TODO this method only exists because we initialize some APIClient in such a
// way that ctx will be nil
func (c *APIClient) ctx() context.Context {
	if c._ctx == nil {
		return c.addMetadata(context.Background())
	}
	return c.addMetadata(c._ctx)
}

func sanitizeErr(err error) error {
	if err == nil {
		return nil
	}

	return errors.New(grpc.ErrorDesc(err))
}
<|endoftext|>


<commit_before>package main

import (
	"common"
	"common/crypto"
	"common/erasure"
	"fmt"
	"network"
)

// global variables
// (with apologies to Haskell)
var (
	mr       common.MessageRouter
	SectorDB map[crypto.Hash]*common.Ring
)

// uploadSector splits a Sector into erasure-coded segments and distributes them across a quorum.
// It creates a Ring from its arguments and stores it in the SectorDB.
func uploadSector(sec *common.Sector, k int, q common.Quorum) (err error) {
	// create ring
	ring, segs, err := erasure.EncodeRing(sec, k)
	if err != nil {
		return
	}
	ring.Hosts = q

	// for now we just send segment i to node i
	// this may need to be randomized for security
	for i := range q {
		m := &common.Message{
			q[i],
			"Server.UploadSegment",
			segs[i],
			nil,
		}
		err = mr.SendMessage(m)
		if err != nil {
			return
		}
	}

	// add ring to SectorDB
	SectorDB[sec.Hash] = ring

	return
}

// downloadSector retrieves the erasure-coded segments corresponding to a given Sector from a quorum.
// It reconstructs the original data from the segments and returns the complete Sector
func downloadSector(hash crypto.Hash) (sec *common.Sector, err error) {
	// retrieve ring from SectorDB
	ring := SectorDB[hash]
	if ring == nil {
		err = fmt.Errorf("hash not present in database")
		return
	}

	// send requests to each member of the quorum
	var segs []common.Segment
	for i := range ring.Hosts {
		var seg common.Segment
		m := &common.Message{
			ring.Hosts[i],
			"Server.DownloadSegment",
			ring.SegHashes[i],
			&seg,
		}
		sendErr := mr.SendMessage(m)
		if sendErr == nil {
			segs = append(segs, seg)
		} else {
			fmt.Println(sendErr)
		}
	}

	// rebuild file
	sec, err = erasure.RebuildSector(ring, segs)
	return
}

func readQuorumAddresses() (q [common.QuorumSize]common.Address) {
	var input int
	for i := range q {
		fmt.Print("Please enter port number ", i, ": ")
		fmt.Scanln(&input)
		q[i] = common.Address{2, "localhost", input}
	}
	return
}

func main() {
	mr, _ = network.NewRPCServer(9989)
	defer mr.Close()
	SectorDB = make(map[crypto.Hash]*common.Ring)
	var (
		input string
		q     [common.QuorumSize]common.Address
		s, rs *common.Sector
		h, rh crypto.Hash
	)
	data, err := crypto.RandomByteSlice(70000)
	s, err = common.NewSector(data)
	h, err = crypto.CalculateHash(data)
	if err != nil {
		fmt.Println("error:", err)
	}
	for {
		fmt.Print("Please enter a command: ")
		fmt.Scanln(&input)

		switch input {
		default:
			fmt.Println("unrecognized command")
		case "j":
			fmt.Println("joining quorum")
			q = readQuorumAddresses()
			fmt.Println("connected to quorum")
		case "u":
			fmt.Println("uploading file")
			err = uploadSector(s, 2, q)
			if err != nil {
				fmt.Println("error:", err)
				fmt.Println("upload failed")
				break
			}
			fmt.Println("upload successful")
			fmt.Println("hash:", h[:])
		case "d":
			fmt.Println("downloading file")
			rs, err = downloadSector(h)
			if err != nil {
				fmt.Println("error:", err)
				fmt.Println("download failed")
				break
			}
			rh, err = crypto.CalculateHash(rs.Data)
			if err != nil {
				fmt.Println("error:", err)
				break
			}
			fmt.Println("download successful")
			fmt.Println("hash:", rh[:])
		case "q":
			return
		}
	}
}
<commit_msg>inline messages<commit_after>package main

import (
	"common"
	"common/crypto"
	"common/erasure"
	"fmt"
	"network"
)

// global variables
// (with apologies to Haskell)
var (
	mr       common.MessageRouter
	SectorDB map[crypto.Hash]*common.Ring
)

// uploadSector splits a Sector into erasure-coded segments and distributes them across a quorum.
// It creates a Ring from its arguments and stores it in the SectorDB.
func uploadSector(sec *common.Sector, k int, q common.Quorum) (err error) {
	// create ring
	ring, segs, err := erasure.EncodeRing(sec, k)
	if err != nil {
		return
	}
	ring.Hosts = q

	// for now we just send segment i to node i
	// this may need to be randomized for security
	for i := range q {
		err = mr.SendMessage(&common.Message{
			Dest: q[i],
			Proc: "Server.UploadSegment",
			Args: segs[i],
			Resp: nil,
		})
		if err != nil {
			return
		}
	}

	// add ring to SectorDB
	SectorDB[sec.Hash] = ring

	return
}

// downloadSector retrieves the erasure-coded segments corresponding to a given Sector from a quorum.
// It reconstructs the original data from the segments and returns the complete Sector
func downloadSector(hash crypto.Hash) (sec *common.Sector, err error) {
	// retrieve ring from SectorDB
	ring := SectorDB[hash]
	if ring == nil {
		err = fmt.Errorf("hash not present in database")
		return
	}

	// send requests to each member of the quorum
	var segs []common.Segment
	for i := range ring.Hosts {
		var seg common.Segment
		sendErr := mr.SendMessage(&common.Message{
			Dest: ring.Hosts[i],
			Proc: "Server.DownloadSegment",
			Args: ring.SegHashes[i],
			Resp: &seg,
		})
		if sendErr == nil {
			segs = append(segs, seg)
		} else {
			fmt.Println(sendErr)
		}
	}

	// rebuild file
	sec, err = erasure.RebuildSector(ring, segs)
	return
}

func readQuorumAddresses() (q [common.QuorumSize]common.Address) {
	var input int
	for i := range q {
		fmt.Print("Please enter port number ", i, ": ")
		fmt.Scanln(&input)
		q[i] = common.Address{2, "localhost", input}
	}
	return
}

func main() {
	mr, _ = network.NewRPCServer(9989)
	defer mr.Close()
	SectorDB = make(map[crypto.Hash]*common.Ring)
	var (
		input string
		q     [common.QuorumSize]common.Address
		s, rs *common.Sector
		h, rh crypto.Hash
	)
	data, err := crypto.RandomByteSlice(70000)
	s, err = common.NewSector(data)
	h, err = crypto.CalculateHash(data)
	if err != nil {
		fmt.Println("error:", err)
	}
	for {
		fmt.Print("Please enter a command: ")
		fmt.Scanln(&input)

		switch input {
		default:
			fmt.Println("unrecognized command")
		case "j":
			fmt.Println("joining quorum")
			q = readQuorumAddresses()
			fmt.Println("connected to quorum")
		case "u":
			fmt.Println("uploading file")
			err = uploadSector(s, 2, q)
			if err != nil {
				fmt.Println("error:", err)
				fmt.Println("upload failed")
				break
			}
			fmt.Println("upload successful")
			fmt.Println("hash:", h[:])
		case "d":
			fmt.Println("downloading file")
			rs, err = downloadSector(h)
			if err != nil {
				fmt.Println("error:", err)
				fmt.Println("download failed")
				break
			}
			rh, err = crypto.CalculateHash(rs.Data)
			if err != nil {
				fmt.Println("error:", err)
				break
			}
			fmt.Println("download successful")
			fmt.Println("hash:", rh[:])
		case "q":
			return
		}
	}
}
<|endoftext|>


<commit_before>package ionic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"

	"github.com/ion-channel/ionic/pagination"
	"github.com/ion-channel/ionic/software_lists"
)

const (
	// GetSoftwareListEndpoint is the path to the endpoint for retrieving a specific Software List
	GetSoftwareListEndpoint = "v1/project/getSBOM"
	// GetSoftwareListsEndpoint is the path to the endpoint for retrieving an organization's Software Lists
	GetSoftwareListsEndpoint = "v1/project/getSBOMs"
	// GetSoftwareListEndpoint is the path to the endpoint for deleting an organization's Software List
	DeleteSoftwareListEndpoint = "/v1/project/deleteSBOM"
	// UpdateSoftwareListEndpoint is the path to the endpoint for updating an organization's Software List
	UpdateSoftwareListEndpoint = "/v1/project/updateSBOM"
)

// GetSoftwareListRequest defines the parameters available for a GetSoftwareList request.
type GetSoftwareListRequest struct {
	// ID (required)	-- Software List ID
	ID string
}

// GetSoftwareListsRequest defines the parameters available for a GetSoftwareLists request.
type GetSoftwareListsRequest struct {
	// OrganizationID (required)	-- Organization ID
	OrganizationID string
	// Status (optional)			-- filters on the Status field of the Software Lists, if provided. Ignored if blank.
	Status string
}

// DeleteSoftwareList deletes the requested Software List or any error that occurred.
func (ic *IonClient) DeleteSoftwareList(id string, token string) error {
	params := url.Values{}
	params.Set("id", id)

	_, err := ic.Delete(DeleteSoftwareListEndpoint, token, params, nil)
	if err != nil {
		return fmt.Errorf("failed to delete SoftwareList: %s", err.Error())
	}

	return nil
}

// UpdateSoftwareList updates the requested Software List or any error that occurred.
func (ic *IonClient) UpdateSoftwareList(sbom software_lists.SoftwareList, token string) (*software_lists.SoftwareList, error) {
	b, err := json.Marshal(sbom)
	if err != nil {
		return nil, fmt.Errorf("session: failed to marshal login body: %v", err.Error())
	}

	buff := bytes.NewBuffer(b)
	b, err = ic.Put(UpdateSoftwareListEndpoint, token, nil, *buff, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to delete SoftwareList: %s", err.Error())
	}

	err = json.Unmarshal(b, &sbom)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal delete response: %s", err.Error())
	}

	return &sbom, nil
}

// GetSoftwareList returns the requested Software List or any error that occurred.
func (ic *IonClient) GetSoftwareList(req GetSoftwareListRequest, token string) (software_lists.SoftwareList, error) {
	var sbom software_lists.SoftwareList

	params := url.Values{}
	params.Set("id", req.ID)

	b, _, err := ic.Get(GetSoftwareListEndpoint, token, params, nil, pagination.Pagination{})
	if err != nil {
		return sbom, fmt.Errorf("failed to get SoftwareList: %s", err.Error())
	}

	err = json.Unmarshal(b, &sbom)
	if err != nil {
		return sbom, fmt.Errorf("failed to unmarshal SoftwareList: %s", err.Error())
	}

	return sbom, nil
}

// GetSoftwareLists retrieves an organization's Software Lists, filtered on the status, if given, or any error that occurred.
func (ic *IonClient) GetSoftwareLists(req GetSoftwareListsRequest, token string) ([]software_lists.SoftwareList, error) {
	params := url.Values{}
	params.Set("org_id", req.OrganizationID)
	params.Set("status", req.Status)

	b, _, err := ic.Get(GetSoftwareListsEndpoint, token, params, nil, pagination.Pagination{})
	if err != nil {
		return nil, fmt.Errorf("failed to get SBOMs: %s", err.Error())
	}

	var sboms []software_lists.SoftwareList
	err = json.Unmarshal(b, &sboms)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal SBOMs: %s", err.Error())
	}

	return sboms, nil
}
<commit_msg>GetSoftwareLists returns a SoftwareList instead of SoftwareInventory<commit_after>package ionic

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/url"

	"github.com/ion-channel/ionic/pagination"
	"github.com/ion-channel/ionic/software_lists"
)

const (
	// GetSoftwareListEndpoint is the path to the endpoint for retrieving a specific Software List
	GetSoftwareListEndpoint = "v1/project/getSBOM"
	// GetSoftwareListsEndpoint is the path to the endpoint for retrieving an organization's Software Lists
	GetSoftwareListsEndpoint = "v1/project/getSBOMs"
	// GetSoftwareListEndpoint is the path to the endpoint for deleting an organization's Software List
	DeleteSoftwareListEndpoint = "/v1/project/deleteSBOM"
	// UpdateSoftwareListEndpoint is the path to the endpoint for updating an organization's Software List
	UpdateSoftwareListEndpoint = "/v1/project/updateSBOM"
)

// GetSoftwareListRequest defines the parameters available for a GetSoftwareList request.
type GetSoftwareListRequest struct {
	// ID (required)	-- Software List ID
	ID string
}

// GetSoftwareListsRequest defines the parameters available for a GetSoftwareLists request.
type GetSoftwareListsRequest struct {
	// OrganizationID (required)	-- Organization ID
	OrganizationID string
	// Status (optional)			-- filters on the Status field of the Software Lists, if provided. Ignored if blank.
	Status string
}

// DeleteSoftwareList deletes the requested Software List or any error that occurred.
func (ic *IonClient) DeleteSoftwareList(id string, token string) error {
	params := url.Values{}
	params.Set("id", id)

	_, err := ic.Delete(DeleteSoftwareListEndpoint, token, params, nil)
	if err != nil {
		return fmt.Errorf("failed to delete SoftwareList: %s", err.Error())
	}

	return nil
}

// UpdateSoftwareList updates the requested Software List or any error that occurred.
func (ic *IonClient) UpdateSoftwareList(sbom software_lists.SoftwareList, token string) (*software_lists.SoftwareList, error) {
	b, err := json.Marshal(sbom)
	if err != nil {
		return nil, fmt.Errorf("session: failed to marshal login body: %v", err.Error())
	}

	buff := bytes.NewBuffer(b)
	b, err = ic.Put(UpdateSoftwareListEndpoint, token, nil, *buff, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to delete SoftwareList: %s", err.Error())
	}

	err = json.Unmarshal(b, &sbom)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal delete response: %s", err.Error())
	}

	return &sbom, nil
}

// GetSoftwareList returns the requested Software List or any error that occurred.
func (ic *IonClient) GetSoftwareList(req GetSoftwareListRequest, token string) (software_lists.SoftwareList, error) {
	var sbom software_lists.SoftwareList

	params := url.Values{}
	params.Set("id", req.ID)

	b, _, err := ic.Get(GetSoftwareListEndpoint, token, params, nil, pagination.Pagination{})
	if err != nil {
		return sbom, fmt.Errorf("failed to get SoftwareList: %s", err.Error())
	}

	err = json.Unmarshal(b, &sbom)
	if err != nil {
		return sbom, fmt.Errorf("failed to unmarshal SoftwareList: %s", err.Error())
	}

	return sbom, nil
}

// GetSoftwareLists retrieves an organization's Software Lists, filtered on the status, if given, or any error that occurred.
func (ic *IonClient) GetSoftwareLists(req GetSoftwareListsRequest, token string) ([]software_lists.SoftwareList, error) {
	var sboms []software_lists.SoftwareList

	params := url.Values{}
	params.Set("org_id", req.OrganizationID)
	params.Set("status", req.Status)

	b, _, err := ic.Get(GetSoftwareListsEndpoint, token, params, nil, pagination.Pagination{})
	if err != nil {
		return nil, fmt.Errorf("failed to get SBOMs: %s", err.Error())
	}

	/* TODO GetSoftwareListsEndpoint should return SoftwareList, not SoftwareInventory.

	   Then we can simply unmarshal a SoftwareList and
	   no need to extract sboms from Inventory.
	*/
	inventory := software_lists.SoftwareInventory{}
	err = json.Unmarshal(b, &inventory)
	if err != nil {
		return nil, fmt.Errorf("failed to unmarshal into sbom: %s %s", err.Error(), b)
	}

	// Extract software list from inventory
	sboms = inventory.SoftwareLists

	return sboms, nil
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"cmd/internal/gc"
	"cmd/internal/obj"
	"cmd/internal/obj/x86"
)

var thechar int = '6'

var thestring string = "amd64"

var thelinkarch *obj.LinkArch = &x86.Linkamd64

func linkarchinit() {
	if obj.Getgoarch() == "amd64p32" {
		thelinkarch = &x86.Linkamd64p32
		gc.Thearch.Thelinkarch = thelinkarch
		thestring = "amd64p32"
		gc.Thearch.Thestring = "amd64p32"
	}
}

var MAXWIDTH int64 = 1 << 50

var addptr int = x86.AADDQ

var movptr int = x86.AMOVQ

var leaptr int = x86.ALEAQ

var cmpptr int = x86.ACMPQ

/*
 * go declares several platform-specific type aliases:
 * int, uint, float, and uintptr
 */
var typedefs = []gc.Typedef{
	gc.Typedef{"int", gc.TINT, gc.TINT64},
	gc.Typedef{"uint", gc.TUINT, gc.TUINT64},
	gc.Typedef{"uintptr", gc.TUINTPTR, gc.TUINT64},
}

func betypeinit() {
	gc.Widthptr = 8
	gc.Widthint = 8
	gc.Widthreg = 8
	if obj.Getgoarch() == "amd64p32" {
		gc.Widthptr = 4
		gc.Widthint = 4
		addptr = x86.AADDL
		movptr = x86.AMOVL
		leaptr = x86.ALEAL
		cmpptr = x86.ACMPL
		typedefs[0].Sameas = gc.TINT32
		typedefs[1].Sameas = gc.TUINT32
		typedefs[2].Sameas = gc.TUINT32
	}

}

func main() {
	if obj.Getgoos() == "nacl" {
		resvd = append(resvd, x86.REG_BP, x86.REG_SI)
	} else if obj.Framepointer_enabled != 0 {
		resvd = append(resvd, x86.REG_BP)
	}

	gc.Thearch.Thechar = thechar
	gc.Thearch.Thestring = thestring
	gc.Thearch.Thelinkarch = thelinkarch
	gc.Thearch.Typedefs = typedefs
	gc.Thearch.REGSP = x86.REGSP
	gc.Thearch.REGCTXT = x86.REGCTXT
	gc.Thearch.REGCALLX = x86.REG_BX
	gc.Thearch.REGCALLX2 = x86.REG_AX
	gc.Thearch.REGRETURN = x86.REG_AX
	gc.Thearch.REGMIN = x86.REG_AX
	gc.Thearch.REGMAX = x86.REG_R15
	gc.Thearch.FREGMIN = x86.REG_X0
	gc.Thearch.FREGMAX = x86.REG_X15
	gc.Thearch.MAXWIDTH = MAXWIDTH
	gc.Thearch.ReservedRegs = resvd

	gc.Thearch.AddIndex = addindex
	gc.Thearch.Betypeinit = betypeinit
	gc.Thearch.Cgen_bmul = cgen_bmul
	gc.Thearch.Cgen_hmul = cgen_hmul
	gc.Thearch.Cgen_shift = cgen_shift
	gc.Thearch.Clearfat = clearfat
	gc.Thearch.Defframe = defframe
	gc.Thearch.Dodiv = dodiv
	gc.Thearch.Excise = excise
	gc.Thearch.Expandchecks = expandchecks
	gc.Thearch.Gins = gins
	gc.Thearch.Ginscon = ginscon
	gc.Thearch.Ginsnop = ginsnop
	gc.Thearch.Gmove = gmove
	gc.Thearch.Linkarchinit = linkarchinit
	gc.Thearch.Peep = peep
	gc.Thearch.Proginfo = proginfo
	gc.Thearch.Regtyp = regtyp
	gc.Thearch.Sameaddr = sameaddr
	gc.Thearch.Smallindir = smallindir
	gc.Thearch.Stackaddr = stackaddr
	gc.Thearch.Stackcopy = stackcopy
	gc.Thearch.Sudoaddable = sudoaddable
	gc.Thearch.Sudoclean = sudoclean
	gc.Thearch.Excludedregs = excludedregs
	gc.Thearch.RtoB = RtoB
	gc.Thearch.FtoB = FtoB
	gc.Thearch.BtoR = BtoR
	gc.Thearch.BtoF = BtoF
	gc.Thearch.Optoas = optoas
	gc.Thearch.Doregbits = doregbits
	gc.Thearch.Regnames = regnames

	gc.Main()
	gc.Exit(0)
}
<commit_msg>cmd/6g: fix build for nacl/amd64p32<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"cmd/internal/gc"
	"cmd/internal/obj"
	"cmd/internal/obj/x86"
)

var thechar int = '6'

var thestring string = "amd64"

var thelinkarch *obj.LinkArch = &x86.Linkamd64

func linkarchinit() {
	if obj.Getgoarch() == "amd64p32" {
		thelinkarch = &x86.Linkamd64p32
		gc.Thearch.Thelinkarch = thelinkarch
		thestring = "amd64p32"
		gc.Thearch.Thestring = "amd64p32"
	}
}

var MAXWIDTH int64 = 1 << 50

var addptr int = x86.AADDQ

var movptr int = x86.AMOVQ

var leaptr int = x86.ALEAQ

var cmpptr int = x86.ACMPQ

/*
 * go declares several platform-specific type aliases:
 * int, uint, float, and uintptr
 */
var typedefs = []gc.Typedef{
	gc.Typedef{"int", gc.TINT, gc.TINT64},
	gc.Typedef{"uint", gc.TUINT, gc.TUINT64},
	gc.Typedef{"uintptr", gc.TUINTPTR, gc.TUINT64},
}

func betypeinit() {
	gc.Widthptr = 8
	gc.Widthint = 8
	gc.Widthreg = 8
	if obj.Getgoarch() == "amd64p32" {
		gc.Widthptr = 4
		gc.Widthint = 4
		addptr = x86.AADDL
		movptr = x86.AMOVL
		leaptr = x86.ALEAL
		cmpptr = x86.ACMPL
		typedefs[0].Sameas = gc.TINT32
		typedefs[1].Sameas = gc.TUINT32
		typedefs[2].Sameas = gc.TUINT32
	}

}

func main() {
	if obj.Getgoos() == "nacl" {
		resvd = append(resvd, x86.REG_BP, x86.REG_R15)
	} else if obj.Framepointer_enabled != 0 {
		resvd = append(resvd, x86.REG_BP)
	}

	gc.Thearch.Thechar = thechar
	gc.Thearch.Thestring = thestring
	gc.Thearch.Thelinkarch = thelinkarch
	gc.Thearch.Typedefs = typedefs
	gc.Thearch.REGSP = x86.REGSP
	gc.Thearch.REGCTXT = x86.REGCTXT
	gc.Thearch.REGCALLX = x86.REG_BX
	gc.Thearch.REGCALLX2 = x86.REG_AX
	gc.Thearch.REGRETURN = x86.REG_AX
	gc.Thearch.REGMIN = x86.REG_AX
	gc.Thearch.REGMAX = x86.REG_R15
	gc.Thearch.FREGMIN = x86.REG_X0
	gc.Thearch.FREGMAX = x86.REG_X15
	gc.Thearch.MAXWIDTH = MAXWIDTH
	gc.Thearch.ReservedRegs = resvd

	gc.Thearch.AddIndex = addindex
	gc.Thearch.Betypeinit = betypeinit
	gc.Thearch.Cgen_bmul = cgen_bmul
	gc.Thearch.Cgen_hmul = cgen_hmul
	gc.Thearch.Cgen_shift = cgen_shift
	gc.Thearch.Clearfat = clearfat
	gc.Thearch.Defframe = defframe
	gc.Thearch.Dodiv = dodiv
	gc.Thearch.Excise = excise
	gc.Thearch.Expandchecks = expandchecks
	gc.Thearch.Gins = gins
	gc.Thearch.Ginscon = ginscon
	gc.Thearch.Ginsnop = ginsnop
	gc.Thearch.Gmove = gmove
	gc.Thearch.Linkarchinit = linkarchinit
	gc.Thearch.Peep = peep
	gc.Thearch.Proginfo = proginfo
	gc.Thearch.Regtyp = regtyp
	gc.Thearch.Sameaddr = sameaddr
	gc.Thearch.Smallindir = smallindir
	gc.Thearch.Stackaddr = stackaddr
	gc.Thearch.Stackcopy = stackcopy
	gc.Thearch.Sudoaddable = sudoaddable
	gc.Thearch.Sudoclean = sudoclean
	gc.Thearch.Excludedregs = excludedregs
	gc.Thearch.RtoB = RtoB
	gc.Thearch.FtoB = FtoB
	gc.Thearch.BtoR = BtoR
	gc.Thearch.BtoF = BtoF
	gc.Thearch.Optoas = optoas
	gc.Thearch.Doregbits = doregbits
	gc.Thearch.Regnames = regnames

	gc.Main()
	gc.Exit(0)
}
<|endoftext|>


<commit_before>package dns

import (
	"net"
	"time"
)

func (p *dnsconfig) probe() error {
	starttime := time.Now()
	if _, err := net.LookupIP(p.hostname); err != nil {
		p.healthy = false
	}
	p.latency = int64(time.Since(starttime) / time.Millisecond)
	p.healthy = true
	return nil
}
<commit_msg>Fix transient true on healthy stat<commit_after>package dns

import (
	"net"
	"time"
)

func (p *dnsconfig) probe() error {
	starttime := time.Now()
	if _, err := net.LookupIP(p.hostname); err != nil {
		p.healthy = false
	} else {
		p.healthy = true
	}
	p.latency = int64(time.Since(starttime) / time.Millisecond)
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*

The godoc program extracts and generates documentation for Go programs.

It has two modes.

Without the -http flag, it prints plain text documentation to standard output and exits.

	godoc fmt
	godoc fmt Printf

With the -http flag, it runs as a web server and presents the documentation as a web page.

	godoc -http=:6060

Usage:
	godoc [flag] package [name ...]

The flags are:
	-v
		verbose mode
	-tabwidth=4
		width of tabs in units of spaces
	-tmplroot="lib/godoc"
		root template directory (if unrooted, relative to --goroot)
	-pkgroot="src/pkg"
		root package source directory (if unrooted, relative to --goroot)
	-html=
		print HTML in command-line mode
	-goroot=$GOROOT
		Go root directory
	-http=
		HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
	-sync="command"
		if this and -sync_minutes are set, run the argument as a
		command every sync_minutes; it is intended to update the
		repository holding the source files.
	-sync_minutes=0
		sync interval in minutes; sync is disabled if <= 0

*/
package documentation
<commit_msg>updated godoc documentation<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*

The godoc program extracts and generates documentation for Go programs.

It has two modes.

Without the -http flag, it prints plain text documentation to standard output and exits.

	godoc fmt
	godoc fmt Printf

With the -http flag, it runs as a web server and presents the documentation as a web page.

	godoc -http=:6060

Usage:
	godoc [flag] package [name ...]

The flags are:
	-v
		verbose mode
	-tabwidth=4
		width of tabs in units of spaces
	-cmdroot="src/cmd"
		root command source directory (if unrooted, relative to -goroot)
	-tmplroot="lib/godoc"
		root template directory (if unrooted, relative to -goroot)
	-pkgroot="src/pkg"
		root package source directory (if unrooted, relative to -goroot)
	-html
		print HTML in command-line mode
	-goroot=$GOROOT
		Go root directory
	-http=
		HTTP service address (e.g., '127.0.0.1:6060' or just ':6060')
	-sync="command"
		if this and -sync_minutes are set, run the argument as a
		command every sync_minutes; it is intended to update the
		repository holding the source files.
	-sync_minutes=0
		sync interval in minutes; sync is disabled if <= 0

When godoc runs as a web server, it creates a search index from all .go files
under $GOROOT (excluding files starting with .). The index is created at startup
and is automatically updated every time the -sync command terminates with exit
status 0, indicating that files have changed.

If the sync exit status is 1, godoc assumes that it succeeded without errors
but that no files changed; the index is not updated in this case.

In all other cases, sync is assumed to have failed and godoc backs off running
sync exponentially (up to 1 day). As soon as sync succeeds again (exit status 0
or 1), the normal sync rhythm is re-established.

*/
package documentation
<|endoftext|>


<commit_before>package protocol

type testRig struct {
}

func (r *testRig) ID() string {
	return "rig"
}

func (r *testRig) IP() string {
	return "127.0.0.1"
}
<commit_msg>fix test<commit_after>package protocol

type testRig struct {
}

func (r *testRig) ID() string {
	return "rig-127.0.0.1"
}

func (r *testRig) IP() string {
	return "127.0.0.1"
}

func (r *testRig) Name() string {
	return "rig"
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/OWASP/Amass/v3/alterations"
	"testing"
)

var subdomain = "fa2ke.owasp.org"

func checkAlterations(generated []string, expected []string) bool {
	return len(generated) == len(expected);
}

func createState() *alterations.State {
	Wordlist := []string{"test", "abc", "123"}
	altState := alterations.NewState(Wordlist)
	altState.MinForWordFlip = 3
	altState.EditDistance = 2
	return altState
}

func TestAlterations(t *testing.T) {
	domain := "www.owasp.org"
	altState := createState(wordlist)
	t.Run("Fuzzy Label subtest", func(t *testing.T) {
		expectedSubdomains := []string{
			"uzyxw.owasp.org",
			"m-1e.owasp.org",
			"0ihgf.owasp.org",
		}
		fuzzyLabel := altState.FuzzyLabelSearches(subdomain)
		if !checkAlterations(fuzzyLabel, expectedSubdomains) {
			t.Errorf("Could not generate all fuzzy label alterations")
		}
	})
	t.Run("flip number label subtest", func(t *testing.T) {
		expectedSubdomains := []string{
			"fa7ke.owasp.org",
			"fa8ke.owasp.org",
			"fa3ke.owasp.org",
		}
		flipNumbers := altState.FlipNumbers(subdomain)
		if !checkAlterations(flipNumbers, expectedSubdomains) {
			t.Errorf("Could not generate all Flip Number")
		}
	})
	t.Run("append number label subtest", func(t *testing.T) {
		expectedSubdomains := []string{
			"fa2ke2.owasp.org",
			"fa2ke3.owasp.org",
			"fa2ke4.owasp.org",
		}
		appendNumbers := altState.AppendNumbers(subdomain)
		if !checkAlterations(appendNumbers, expectedSubdomains) {
			t.Errorf("Could not generate all append number label alterations")
		}
	})
	t.Run("flip word label subtest", func(t *testing.T) {
		expectedSubdomains := []string{}
		flipWords := altState.FlipWords(subdomain)
		if !checkAlterations(flipWords, expectedSubdomains) {
			t.Errorf("Could not generate all flip words label alterations")
		}
	})
	t.Run("suffix label subtest", func(t *testing.T) {
		expectedSubdomains := []string{}
		addSuffix := altState.AddSuffixWord(subdomain)
		if !checkAlterations(addSuffix, expectedSubdomains) {
			t.Errorf("Could not generate all add suffix label alterations")
		}
	})
	t.Run("prefix number label subtest", func(t *testing.T) {
		expectedSubdomains := []string{}
		addPrefix := altState.AddPrefixWord(subdomain)
		if !checkAlterations(addPrefix, expectedSubdomains) {
			t.Errorf("Could not generate all add prefix number label alterations")
		}
	})
}
<commit_msg>the tests do not currently work<commit_after>package alterations
/*
import (
	"testing"
)

func checkAlterations(generated []string, expected []string) bool {
	return len(generated) == len(expected)
}

func createState() *State {
	altState := NewState([]string{"test", "abc", "123"})

	altState.MinForWordFlip = 3
	altState.EditDistance = 2

	return altState
}

func TestAlterations(t *testing.T) {
	fake := "fa2ke.owasp.org"
	altState := createState()

	t.Run("Fuzzy Label subtest", func(t *testing.T) {
		expected := []string{
			"uzyxw.owasp.org",
			"m-1e.owasp.org",
			"0ihgf.owasp.org",
		}

		fuzzyLabel := altState.FuzzyLabelSearches(fake)
		if !checkAlterations(fuzzyLabel, expected) {
			t.Errorf("Could not generate all fuzzy label alterations")
		}
	})

	t.Run("flip number label subtest", func(t *testing.T) {
		expected := []string{
			"fa0ke.owasp.org",
		}

		flipNumbers := altState.FlipNumbers(fake)
		if !checkAlterations(flipNumbers, expected) {
			t.Errorf("Could not generate all Flip Number")
		}
	})

	t.Run("append number label subtest", func(t *testing.T) {
		var expected []string
		for i := 0; i < 10; i++ {
			expected = append(expected, "fa2ke" + strconv.Itoa(i) + ".owasp.org")
		}

		appendNumbers := altState.AppendNumbers(fake)
		if !checkAlterations(appendNumbers, expected) {
			t.Errorf("Could not generate all append number label alterations")
		}
	})

	t.Run("flip word label subtest", func(t *testing.T) {
		expected := []string{}
		flipWords := altState.FlipWords(fake)

		if !checkAlterations(flipWords, expected) {
			t.Errorf("Could not generate all flip words label alterations")
		}
	})

	t.Run("suffix label subtest", func(t *testing.T) {
		expected := []string{}
		addSuffix := altState.AddSuffixWord(fake)

		if !checkAlterations(addSuffix, expected) {
			t.Errorf("Could not generate all add suffix label alterations")
		}
	})

	t.Run("prefix number label subtest", func(t *testing.T) {
		expected := []string{}
		addPrefix := altState.AddPrefixWord(fake)

		if !checkAlterations(addPrefix, expected) {
			t.Errorf("Could not generate all add prefix number label alterations")
		}
	})
}
*/<|endoftext|>


<commit_before>package postgresql

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/vault/sdk/physical"

	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-uuid"

	"github.com/armon/go-metrics"
	"github.com/lib/pq"
)

const (

	// The lock TTL matches the default that Consul API uses, 15 seconds.
	// Used as part of SQL commands to set/extend lock expiry time relative to
	// database clock.
	PostgreSQLLockTTLSeconds = 15

	// The amount of time to wait between the lock renewals
	PostgreSQLLockRenewInterval = 5 * time.Second

	// PostgreSQLLockRetryInterval is the amount of time to wait
	// if a lock fails before trying again.
	PostgreSQLLockRetryInterval = time.Second
)

// Verify PostgreSQLBackend satisfies the correct interfaces
var _ physical.Backend = (*PostgreSQLBackend)(nil)

//
// HA backend was implemented based on the DynamoDB backend pattern
// With distinction using central postgres clock, hereby avoiding
// possible issues with multiple clocks
//
var _ physical.HABackend = (*PostgreSQLBackend)(nil)
var _ physical.Lock = (*PostgreSQLLock)(nil)

// PostgreSQL Backend is a physical backend that stores data
// within a PostgreSQL database.
type PostgreSQLBackend struct {
	table        string
	client       *sql.DB
	put_query    string
	get_query    string
	delete_query string
	list_query   string

	ha_table                 string
	haGetLockValueQuery      string
	haUpsertLockIdentityExec string
	haDeleteLockExec         string

	haEnabled  bool
	logger     log.Logger
	permitPool *physical.PermitPool
}

// PostgreSQLLock implements a lock using an PostgreSQL client.
type PostgreSQLLock struct {
	backend    *PostgreSQLBackend
	value, key string
	identity   string
	lock       sync.Mutex

	renewTicker *time.Ticker

	// ttlSeconds is how long a lock is valid for
	ttlSeconds int

	// renewInterval is how much time to wait between lock renewals.  must be << ttl
	renewInterval time.Duration

	// retryInterval is how much time to wait between attempts to grab the lock
	retryInterval time.Duration
}

// NewPostgreSQLBackend constructs a PostgreSQL backend using the given
// API client, server address, credentials, and database.
func NewPostgreSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
	// Get the PostgreSQL credentials to perform read/write operations.
	connURL, ok := conf["connection_url"]
	if !ok || connURL == "" {
		return nil, fmt.Errorf("missing connection_url")
	}

	unquoted_table, ok := conf["table"]
	if !ok {
		unquoted_table = "vault_kv_store"
	}
	quoted_table := pq.QuoteIdentifier(unquoted_table)

	maxParStr, ok := conf["max_parallel"]
	var maxParInt int
	var err error
	if ok {
		maxParInt, err = strconv.Atoi(maxParStr)
		if err != nil {
			return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
		}
		if logger.IsDebug() {
			logger.Debug("max_parallel set", "max_parallel", maxParInt)
		}
	} else {
		maxParInt = physical.DefaultParallelOperations
	}

	// Create PostgreSQL handle for the database.
	db, err := sql.Open("postgres", connURL)
	if err != nil {
		return nil, errwrap.Wrapf("failed to connect to postgres: {{err}}", err)
	}
	db.SetMaxOpenConns(maxParInt)

	// Determine if we should use a function to work around lack of upsert (versions < 9.5)
	var upsertAvailable bool
	upsertAvailableQuery := "SELECT current_setting('server_version_num')::int >= 90500"
	if err := db.QueryRow(upsertAvailableQuery).Scan(&upsertAvailable); err != nil {
		return nil, errwrap.Wrapf("failed to check for native upsert: {{err}}", err)
	}

	if !upsertAvailable && conf["ha_enabled"] == "true" {
		return nil, fmt.Errorf("ha_enabled=true in config but PG version doesn't support HA, must be at least 9.5")
	}

	// Setup our put strategy based on the presence or absence of a native
	// upsert.
	var put_query string
	if !upsertAvailable {
		put_query = "SELECT vault_kv_put($1, $2, $3, $4)"
	} else {
		put_query = "INSERT INTO " + quoted_table + " VALUES($1, $2, $3, $4)" +
			" ON CONFLICT (path, key) DO " +
			" UPDATE SET (parent_path, path, key, value) = ($1, $2, $3, $4)"
	}

	unquoted_ha_table, ok := conf["ha_table"]
	if !ok {
		unquoted_ha_table = "vault_ha_locks"
	}
	quoted_ha_table := pq.QuoteIdentifier(unquoted_ha_table)

	// Setup the backend.
	m := &PostgreSQLBackend{
		table:        quoted_table,
		client:       db,
		put_query:    put_query,
		get_query:    "SELECT value FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		delete_query: "DELETE FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		list_query: "SELECT key FROM " + quoted_table + " WHERE path = $1" +
			" UNION SELECT DISTINCT substring(substr(path, length($1)+1) from '^.*?/') FROM " + quoted_table +
			" WHERE parent_path LIKE $1 || '%'",
		haGetLockValueQuery:
		// only read non expired data
		" SELECT ha_value FROM " + quoted_ha_table + " WHERE NOW() <= valid_until AND ha_key = $1 ",
		haUpsertLockIdentityExec:
		// $1=identity $2=ha_key $3=ha_value $4=TTL in seconds
		// update either steal expired lock OR update expiry for lock owned by me
		" INSERT INTO " + quoted_ha_table + " as t (ha_identity, ha_key, ha_value, valid_until) VALUES ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds'  ) " +
			" ON CONFLICT (ha_key) DO " +
			" UPDATE SET (ha_identity, ha_key, ha_value, valid_until) = ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds') " +
			" WHERE (t.valid_until < NOW() AND t.ha_key = $2) OR " +
			" (t.ha_identity = $1 AND t.ha_key = $2)  ",
		haDeleteLockExec:
		// $1=ha_identity $2=ha_key
		" DELETE FROM " + quoted_ha_table + " WHERE ha_identity=$1 AND ha_key=$2 ",
		logger:     logger,
		permitPool: physical.NewPermitPool(maxParInt),
		haEnabled:  conf["ha_enabled"] == "true",
	}

	return m, nil
}

// splitKey is a helper to split a full path key into individual
// parts: parentPath, path, key
func (m *PostgreSQLBackend) splitKey(fullPath string) (string, string, string) {
	var parentPath string
	var path string

	pieces := strings.Split(fullPath, "/")
	depth := len(pieces)
	key := pieces[depth-1]

	if depth == 1 {
		parentPath = ""
		path = "/"
	} else if depth == 2 {
		parentPath = "/"
		path = "/" + pieces[0] + "/"
	} else {
		parentPath = "/" + strings.Join(pieces[:depth-2], "/") + "/"
		path = "/" + strings.Join(pieces[:depth-1], "/") + "/"
	}

	return parentPath, path, key
}

// Put is used to insert or update an entry.
func (m *PostgreSQLBackend) Put(ctx context.Context, entry *physical.Entry) error {
	defer metrics.MeasureSince([]string{"postgres", "put"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	parentPath, path, key := m.splitKey(entry.Key)

	_, err := m.client.Exec(m.put_query, parentPath, path, key, entry.Value)
	if err != nil {
		return err
	}
	return nil
}

// Get is used to fetch and entry.
func (m *PostgreSQLBackend) Get(ctx context.Context, fullPath string) (*physical.Entry, error) {
	defer metrics.MeasureSince([]string{"postgres", "get"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	var result []byte
	err := m.client.QueryRow(m.get_query, path, key).Scan(&result)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	ent := &physical.Entry{
		Key:   fullPath,
		Value: result,
	}
	return ent, nil
}

// Delete is used to permanently delete an entry
func (m *PostgreSQLBackend) Delete(ctx context.Context, fullPath string) error {
	defer metrics.MeasureSince([]string{"postgres", "delete"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	_, err := m.client.Exec(m.delete_query, path, key)
	if err != nil {
		return err
	}
	return nil
}

// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (m *PostgreSQLBackend) List(ctx context.Context, prefix string) ([]string, error) {
	defer metrics.MeasureSince([]string{"postgres", "list"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	rows, err := m.client.Query(m.list_query, "/"+prefix)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var keys []string
	for rows.Next() {
		var key string
		err = rows.Scan(&key)
		if err != nil {
			return nil, errwrap.Wrapf("failed to scan rows: {{err}}", err)
		}

		keys = append(keys, key)
	}

	return keys, nil
}

// LockWith is used for mutual exclusion based on the given key.
func (p *PostgreSQLBackend) LockWith(key, value string) (physical.Lock, error) {
	identity, err := uuid.GenerateUUID()
	if err != nil {
		return nil, err
	}
	return &PostgreSQLLock{
		backend:       p,
		key:           key,
		value:         value,
		identity:      identity,
		ttlSeconds:    PostgreSQLLockTTLSeconds,
		renewInterval: PostgreSQLLockRenewInterval,
		retryInterval: PostgreSQLLockRetryInterval,
	}, nil
}

func (p *PostgreSQLBackend) HAEnabled() bool {
	return p.haEnabled
}

// Lock tries to acquire the lock by repeatedly trying to create a record in the
// PostgreSQL table. It will block until either the stop channel is closed or
// the lock could be acquired successfully. The returned channel will be closed
// once the lock in the PostgreSQL table cannot be renewed, either due to an
// error speaking to PostgresSQL or because someone else has taken it.
func (l *PostgreSQLLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
	l.lock.Lock()
	defer l.lock.Unlock()

	var (
		success = make(chan struct{})
		errors  = make(chan error)
		leader  = make(chan struct{})
	)
	// try to acquire the lock asynchronously
	go l.tryToLock(stopCh, success, errors)

	select {
	case <-success:
		// after acquiring it successfully, we must renew the lock periodically
		l.renewTicker = time.NewTicker(l.renewInterval)
		go l.periodicallyRenewLock(leader)
	case err := <-errors:
		return nil, err
	case <-stopCh:
		return nil, nil
	}

	return leader, nil
}

// Unlock releases the lock by deleting the lock record from the
// PostgreSQL table.
func (l *PostgreSQLLock) Unlock() error {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	if l.renewTicker != nil {
		l.renewTicker.Stop()
	}

	// Delete lock owned by me
	_, err := pg.client.Exec(pg.haDeleteLockExec, l.identity, l.key)
	return err
}

// Value checks whether or not the lock is held by any instance of PostgreSQLLock,
// including this one, and returns the current value.
func (l *PostgreSQLLock) Value() (bool, string, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()
	var result string
	err := pg.client.QueryRow(pg.haGetLockValueQuery, l.key).Scan(&result)

	switch err {
	case nil:
		return true, result, nil
	case sql.ErrNoRows:
		return false, "", nil
	default:
		return false, "", err

	}
}

// tryToLock tries to create a new item in PostgreSQL every `retryInterval`.
// As long as the item cannot be created (because it already exists), it will
// be retried. If the operation fails due to an error, it is sent to the errors
// channel. When the lock could be acquired successfully, the success channel
// is closed.
func (l *PostgreSQLLock) tryToLock(stop <-chan struct{}, success chan struct{}, errors chan error) {
	ticker := time.NewTicker(l.retryInterval)
	defer ticker.Stop()

	for {
		select {
		case <-stop:
			return
		case <-ticker.C:
			gotlock, err := l.writeItem()
			switch {
			case err != nil:
				errors <- err
				return
			case gotlock:
				close(success)
				return
			}
		}
	}
}

func (l *PostgreSQLLock) periodicallyRenewLock(done chan struct{}) {
	for range l.renewTicker.C {
		gotlock, err := l.writeItem()
		if err != nil || !gotlock {
			close(done)
			l.renewTicker.Stop()
			return
		}
	}
}

// Attempts to put/update the PostgreSQL item using condition expressions to
// evaluate the TTL.  Returns true if the lock was obtained, false if not.
// If false error may be nil or non-nil: nil indicates simply that someone
// else has the lock, whereas non-nil means that something unexpected happened.
func (l *PostgreSQLLock) writeItem() (bool, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	// Try steal lock or update expiry on my lock

	sqlResult, err := pg.client.Exec(pg.haUpsertLockIdentityExec, l.identity, l.key, l.value, l.ttlSeconds)
	if err != nil {
		return false, err
	}
	if sqlResult == nil {
		return false, fmt.Errorf("empty SQL response received")
	}

	ar, err := sqlResult.RowsAffected()
	if err != nil {
		return false, err
	}
	return ar == 1, nil
}
<commit_msg>Since the two branches of the UNION produce disjoint sets, do a UNION ALL (#6546)<commit_after>package postgresql

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/vault/sdk/physical"

	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-uuid"

	"github.com/armon/go-metrics"
	"github.com/lib/pq"
)

const (

	// The lock TTL matches the default that Consul API uses, 15 seconds.
	// Used as part of SQL commands to set/extend lock expiry time relative to
	// database clock.
	PostgreSQLLockTTLSeconds = 15

	// The amount of time to wait between the lock renewals
	PostgreSQLLockRenewInterval = 5 * time.Second

	// PostgreSQLLockRetryInterval is the amount of time to wait
	// if a lock fails before trying again.
	PostgreSQLLockRetryInterval = time.Second
)

// Verify PostgreSQLBackend satisfies the correct interfaces
var _ physical.Backend = (*PostgreSQLBackend)(nil)

//
// HA backend was implemented based on the DynamoDB backend pattern
// With distinction using central postgres clock, hereby avoiding
// possible issues with multiple clocks
//
var _ physical.HABackend = (*PostgreSQLBackend)(nil)
var _ physical.Lock = (*PostgreSQLLock)(nil)

// PostgreSQL Backend is a physical backend that stores data
// within a PostgreSQL database.
type PostgreSQLBackend struct {
	table        string
	client       *sql.DB
	put_query    string
	get_query    string
	delete_query string
	list_query   string

	ha_table                 string
	haGetLockValueQuery      string
	haUpsertLockIdentityExec string
	haDeleteLockExec         string

	haEnabled  bool
	logger     log.Logger
	permitPool *physical.PermitPool
}

// PostgreSQLLock implements a lock using an PostgreSQL client.
type PostgreSQLLock struct {
	backend    *PostgreSQLBackend
	value, key string
	identity   string
	lock       sync.Mutex

	renewTicker *time.Ticker

	// ttlSeconds is how long a lock is valid for
	ttlSeconds int

	// renewInterval is how much time to wait between lock renewals.  must be << ttl
	renewInterval time.Duration

	// retryInterval is how much time to wait between attempts to grab the lock
	retryInterval time.Duration
}

// NewPostgreSQLBackend constructs a PostgreSQL backend using the given
// API client, server address, credentials, and database.
func NewPostgreSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
	// Get the PostgreSQL credentials to perform read/write operations.
	connURL, ok := conf["connection_url"]
	if !ok || connURL == "" {
		return nil, fmt.Errorf("missing connection_url")
	}

	unquoted_table, ok := conf["table"]
	if !ok {
		unquoted_table = "vault_kv_store"
	}
	quoted_table := pq.QuoteIdentifier(unquoted_table)

	maxParStr, ok := conf["max_parallel"]
	var maxParInt int
	var err error
	if ok {
		maxParInt, err = strconv.Atoi(maxParStr)
		if err != nil {
			return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err)
		}
		if logger.IsDebug() {
			logger.Debug("max_parallel set", "max_parallel", maxParInt)
		}
	} else {
		maxParInt = physical.DefaultParallelOperations
	}

	// Create PostgreSQL handle for the database.
	db, err := sql.Open("postgres", connURL)
	if err != nil {
		return nil, errwrap.Wrapf("failed to connect to postgres: {{err}}", err)
	}
	db.SetMaxOpenConns(maxParInt)

	// Determine if we should use a function to work around lack of upsert (versions < 9.5)
	var upsertAvailable bool
	upsertAvailableQuery := "SELECT current_setting('server_version_num')::int >= 90500"
	if err := db.QueryRow(upsertAvailableQuery).Scan(&upsertAvailable); err != nil {
		return nil, errwrap.Wrapf("failed to check for native upsert: {{err}}", err)
	}

	if !upsertAvailable && conf["ha_enabled"] == "true" {
		return nil, fmt.Errorf("ha_enabled=true in config but PG version doesn't support HA, must be at least 9.5")
	}

	// Setup our put strategy based on the presence or absence of a native
	// upsert.
	var put_query string
	if !upsertAvailable {
		put_query = "SELECT vault_kv_put($1, $2, $3, $4)"
	} else {
		put_query = "INSERT INTO " + quoted_table + " VALUES($1, $2, $3, $4)" +
			" ON CONFLICT (path, key) DO " +
			" UPDATE SET (parent_path, path, key, value) = ($1, $2, $3, $4)"
	}

	unquoted_ha_table, ok := conf["ha_table"]
	if !ok {
		unquoted_ha_table = "vault_ha_locks"
	}
	quoted_ha_table := pq.QuoteIdentifier(unquoted_ha_table)

	// Setup the backend.
	m := &PostgreSQLBackend{
		table:        quoted_table,
		client:       db,
		put_query:    put_query,
		get_query:    "SELECT value FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		delete_query: "DELETE FROM " + quoted_table + " WHERE path = $1 AND key = $2",
		list_query: "SELECT key FROM " + quoted_table + " WHERE path = $1" +
			" UNION ALL SELECT DISTINCT substring(substr(path, length($1)+1) from '^.*?/') FROM " + quoted_table +
			" WHERE parent_path LIKE $1 || '%'",
		haGetLockValueQuery:
		// only read non expired data
		" SELECT ha_value FROM " + quoted_ha_table + " WHERE NOW() <= valid_until AND ha_key = $1 ",
		haUpsertLockIdentityExec:
		// $1=identity $2=ha_key $3=ha_value $4=TTL in seconds
		// update either steal expired lock OR update expiry for lock owned by me
		" INSERT INTO " + quoted_ha_table + " as t (ha_identity, ha_key, ha_value, valid_until) VALUES ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds'  ) " +
			" ON CONFLICT (ha_key) DO " +
			" UPDATE SET (ha_identity, ha_key, ha_value, valid_until) = ($1, $2, $3, NOW() + $4 * INTERVAL '1 seconds') " +
			" WHERE (t.valid_until < NOW() AND t.ha_key = $2) OR " +
			" (t.ha_identity = $1 AND t.ha_key = $2)  ",
		haDeleteLockExec:
		// $1=ha_identity $2=ha_key
		" DELETE FROM " + quoted_ha_table + " WHERE ha_identity=$1 AND ha_key=$2 ",
		logger:     logger,
		permitPool: physical.NewPermitPool(maxParInt),
		haEnabled:  conf["ha_enabled"] == "true",
	}

	return m, nil
}

// splitKey is a helper to split a full path key into individual
// parts: parentPath, path, key
func (m *PostgreSQLBackend) splitKey(fullPath string) (string, string, string) {
	var parentPath string
	var path string

	pieces := strings.Split(fullPath, "/")
	depth := len(pieces)
	key := pieces[depth-1]

	if depth == 1 {
		parentPath = ""
		path = "/"
	} else if depth == 2 {
		parentPath = "/"
		path = "/" + pieces[0] + "/"
	} else {
		parentPath = "/" + strings.Join(pieces[:depth-2], "/") + "/"
		path = "/" + strings.Join(pieces[:depth-1], "/") + "/"
	}

	return parentPath, path, key
}

// Put is used to insert or update an entry.
func (m *PostgreSQLBackend) Put(ctx context.Context, entry *physical.Entry) error {
	defer metrics.MeasureSince([]string{"postgres", "put"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	parentPath, path, key := m.splitKey(entry.Key)

	_, err := m.client.Exec(m.put_query, parentPath, path, key, entry.Value)
	if err != nil {
		return err
	}
	return nil
}

// Get is used to fetch and entry.
func (m *PostgreSQLBackend) Get(ctx context.Context, fullPath string) (*physical.Entry, error) {
	defer metrics.MeasureSince([]string{"postgres", "get"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	var result []byte
	err := m.client.QueryRow(m.get_query, path, key).Scan(&result)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}

	ent := &physical.Entry{
		Key:   fullPath,
		Value: result,
	}
	return ent, nil
}

// Delete is used to permanently delete an entry
func (m *PostgreSQLBackend) Delete(ctx context.Context, fullPath string) error {
	defer metrics.MeasureSince([]string{"postgres", "delete"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	_, path, key := m.splitKey(fullPath)

	_, err := m.client.Exec(m.delete_query, path, key)
	if err != nil {
		return err
	}
	return nil
}

// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (m *PostgreSQLBackend) List(ctx context.Context, prefix string) ([]string, error) {
	defer metrics.MeasureSince([]string{"postgres", "list"}, time.Now())

	m.permitPool.Acquire()
	defer m.permitPool.Release()

	rows, err := m.client.Query(m.list_query, "/"+prefix)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var keys []string
	for rows.Next() {
		var key string
		err = rows.Scan(&key)
		if err != nil {
			return nil, errwrap.Wrapf("failed to scan rows: {{err}}", err)
		}

		keys = append(keys, key)
	}

	return keys, nil
}

// LockWith is used for mutual exclusion based on the given key.
func (p *PostgreSQLBackend) LockWith(key, value string) (physical.Lock, error) {
	identity, err := uuid.GenerateUUID()
	if err != nil {
		return nil, err
	}
	return &PostgreSQLLock{
		backend:       p,
		key:           key,
		value:         value,
		identity:      identity,
		ttlSeconds:    PostgreSQLLockTTLSeconds,
		renewInterval: PostgreSQLLockRenewInterval,
		retryInterval: PostgreSQLLockRetryInterval,
	}, nil
}

func (p *PostgreSQLBackend) HAEnabled() bool {
	return p.haEnabled
}

// Lock tries to acquire the lock by repeatedly trying to create a record in the
// PostgreSQL table. It will block until either the stop channel is closed or
// the lock could be acquired successfully. The returned channel will be closed
// once the lock in the PostgreSQL table cannot be renewed, either due to an
// error speaking to PostgresSQL or because someone else has taken it.
func (l *PostgreSQLLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
	l.lock.Lock()
	defer l.lock.Unlock()

	var (
		success = make(chan struct{})
		errors  = make(chan error)
		leader  = make(chan struct{})
	)
	// try to acquire the lock asynchronously
	go l.tryToLock(stopCh, success, errors)

	select {
	case <-success:
		// after acquiring it successfully, we must renew the lock periodically
		l.renewTicker = time.NewTicker(l.renewInterval)
		go l.periodicallyRenewLock(leader)
	case err := <-errors:
		return nil, err
	case <-stopCh:
		return nil, nil
	}

	return leader, nil
}

// Unlock releases the lock by deleting the lock record from the
// PostgreSQL table.
func (l *PostgreSQLLock) Unlock() error {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	if l.renewTicker != nil {
		l.renewTicker.Stop()
	}

	// Delete lock owned by me
	_, err := pg.client.Exec(pg.haDeleteLockExec, l.identity, l.key)
	return err
}

// Value checks whether or not the lock is held by any instance of PostgreSQLLock,
// including this one, and returns the current value.
func (l *PostgreSQLLock) Value() (bool, string, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()
	var result string
	err := pg.client.QueryRow(pg.haGetLockValueQuery, l.key).Scan(&result)

	switch err {
	case nil:
		return true, result, nil
	case sql.ErrNoRows:
		return false, "", nil
	default:
		return false, "", err

	}
}

// tryToLock tries to create a new item in PostgreSQL every `retryInterval`.
// As long as the item cannot be created (because it already exists), it will
// be retried. If the operation fails due to an error, it is sent to the errors
// channel. When the lock could be acquired successfully, the success channel
// is closed.
func (l *PostgreSQLLock) tryToLock(stop <-chan struct{}, success chan struct{}, errors chan error) {
	ticker := time.NewTicker(l.retryInterval)
	defer ticker.Stop()

	for {
		select {
		case <-stop:
			return
		case <-ticker.C:
			gotlock, err := l.writeItem()
			switch {
			case err != nil:
				errors <- err
				return
			case gotlock:
				close(success)
				return
			}
		}
	}
}

func (l *PostgreSQLLock) periodicallyRenewLock(done chan struct{}) {
	for range l.renewTicker.C {
		gotlock, err := l.writeItem()
		if err != nil || !gotlock {
			close(done)
			l.renewTicker.Stop()
			return
		}
	}
}

// Attempts to put/update the PostgreSQL item using condition expressions to
// evaluate the TTL.  Returns true if the lock was obtained, false if not.
// If false error may be nil or non-nil: nil indicates simply that someone
// else has the lock, whereas non-nil means that something unexpected happened.
func (l *PostgreSQLLock) writeItem() (bool, error) {
	pg := l.backend
	pg.permitPool.Acquire()
	defer pg.permitPool.Release()

	// Try steal lock or update expiry on my lock

	sqlResult, err := pg.client.Exec(pg.haUpsertLockIdentityExec, l.identity, l.key, l.value, l.ttlSeconds)
	if err != nil {
		return false, err
	}
	if sqlResult == nil {
		return false, fmt.Errorf("empty SQL response received")
	}

	ar, err := sqlResult.RowsAffected()
	if err != nil {
		return false, err
	}
	return ar == 1, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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 v1beta1

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	HabitatResourcePlural = "habitats"
	HabitatShortName      = "hab"

	// HabitatLabel labels the resources that belong to Habitat.
	// Example: 'habitat: true'
	HabitatLabel = "habitat"
	// HabitatNameLabel contains the user defined Habitat Service name.
	// Example: 'habitat-name: db'
	HabitatNameLabel = "habitat-name"

	TopologyLabel = "topology"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type Habitat struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`
	Spec              HabitatSpec   `json:"spec"`
	Status            HabitatStatus `json:"status,omitempty"`
	// CustomVersion is a field that works around the lack of support for running
	// multiple versions of a CDR.  It encodes the actual version of the type, so
	// that controllers can decide whether to discard an object if the version
	// doesn't match.
	// +optional
	CustomVersion *string `json:"customVersion,omitempty"`
}

type HabitatSpec struct {
	// Count is the amount of Services to start in this Habitat.
	Count int `json:"count"`
	// Image is the Docker image of the Habitat Service.
	Image   string  `json:"image"`
	Service Service `json:"service"`
	// Env is a list of environment variables.
	// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core.
	// Optional.
	Env []corev1.EnvVar `json:"env,omitempty"`
	// +optional
	PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"`
}

// PersistentStorage contains the details of the persistent storage that the
// cluster should provision.
type PersistentStorage struct {
	// Size is the volume's size.
	// It uses the same format as Kubernetes' size fields, e.g. 10Gi
	Size string `json:"size"`
	// MountPath is the path at which the PersistentVolume will be mounted.
	MountPath string `json:"mountPath"`
	// StorageClassName is the name of the StorageClass that the StatefulSet will request.
	StorageClassName string `json:"storageClassName"`
}

type HabitatStatus struct {
	State   HabitatState `json:"state,omitempty"`
	Message string       `json:"message,omitempty"`
}

type HabitatState string

type Service struct {
	// Group is the value of the --group flag for the hab client.
	// Optional. Defaults to `default`.
	Group string `json:"group"`
	// Topology is the value of the --topology flag for the hab client.
	Topology `json:"topology"`
	// ConfigSecretName is the name of a Secret containing a Habitat service's config in TOML format.
	// It will be mounted inside the pod as a file, and it will be used by Habitat to configure the service.
	// Optional.
	ConfigSecretName string `json:"configSecretName,omitempty"`
	// The name of the secret that contains the ring key.
	// Optional.
	RingSecretName string `json:"ringSecretName,omitempty"`
	// Bind is when one service connects to another forming a producer/consumer relationship.
	// Optional.
	Bind []Bind `json:"bind,omitempty"`
	// Name is the name of the Habitat service that this Habitat object represents.
	// This field is used to mount the user.toml file in the correct directory under /hab/user/ in the Pod.
	Name string `json:"name"`
}

type Bind struct {
	// Name is the name of the bind specified in the Habitat configuration files.
	Name string `json:"name"`
	// Service is the name of the service this bind refers to.
	Service string `json:"service"`
	// Group is the group of the service this bind refers to.
	Group string `json:"group"`
}

type Topology string

func (t Topology) String() string {
	return string(t)
}

const (
	HabitatStateCreated   HabitatState = "Created"
	HabitatStateProcessed HabitatState = "Processed"

	TopologyStandalone Topology = "standalone"
	TopologyLeader     Topology = "leader"

	HabitatKind = "Habitat"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type HabitatList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []Habitat `json:"items"`
}
<commit_msg>habitat/v1beta1: Document default case<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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 v1beta1

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	HabitatResourcePlural = "habitats"
	HabitatShortName      = "hab"

	// HabitatLabel labels the resources that belong to Habitat.
	// Example: 'habitat: true'
	HabitatLabel = "habitat"
	// HabitatNameLabel contains the user defined Habitat Service name.
	// Example: 'habitat-name: db'
	HabitatNameLabel = "habitat-name"

	TopologyLabel = "topology"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type Habitat struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`
	Spec              HabitatSpec   `json:"spec"`
	Status            HabitatStatus `json:"status,omitempty"`
	// CustomVersion is a field that works around the lack of support for running
	// multiple versions of a CDR.  It encodes the actual version of the type, so
	// that controllers can decide whether to discard an object if the version
	// doesn't match.
	// When absent, it defaults to v1beta1.
	// +optional
	CustomVersion *string `json:"customVersion,omitempty"`
}

type HabitatSpec struct {
	// Count is the amount of Services to start in this Habitat.
	Count int `json:"count"`
	// Image is the Docker image of the Habitat Service.
	Image   string  `json:"image"`
	Service Service `json:"service"`
	// Env is a list of environment variables.
	// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core.
	// Optional.
	Env []corev1.EnvVar `json:"env,omitempty"`
	// +optional
	PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"`
}

// PersistentStorage contains the details of the persistent storage that the
// cluster should provision.
type PersistentStorage struct {
	// Size is the volume's size.
	// It uses the same format as Kubernetes' size fields, e.g. 10Gi
	Size string `json:"size"`
	// MountPath is the path at which the PersistentVolume will be mounted.
	MountPath string `json:"mountPath"`
	// StorageClassName is the name of the StorageClass that the StatefulSet will request.
	StorageClassName string `json:"storageClassName"`
}

type HabitatStatus struct {
	State   HabitatState `json:"state,omitempty"`
	Message string       `json:"message,omitempty"`
}

type HabitatState string

type Service struct {
	// Group is the value of the --group flag for the hab client.
	// Optional. Defaults to `default`.
	Group string `json:"group"`
	// Topology is the value of the --topology flag for the hab client.
	Topology `json:"topology"`
	// ConfigSecretName is the name of a Secret containing a Habitat service's config in TOML format.
	// It will be mounted inside the pod as a file, and it will be used by Habitat to configure the service.
	// Optional.
	ConfigSecretName string `json:"configSecretName,omitempty"`
	// The name of the secret that contains the ring key.
	// Optional.
	RingSecretName string `json:"ringSecretName,omitempty"`
	// Bind is when one service connects to another forming a producer/consumer relationship.
	// Optional.
	Bind []Bind `json:"bind,omitempty"`
	// Name is the name of the Habitat service that this Habitat object represents.
	// This field is used to mount the user.toml file in the correct directory under /hab/user/ in the Pod.
	Name string `json:"name"`
}

type Bind struct {
	// Name is the name of the bind specified in the Habitat configuration files.
	Name string `json:"name"`
	// Service is the name of the service this bind refers to.
	Service string `json:"service"`
	// Group is the group of the service this bind refers to.
	Group string `json:"group"`
}

type Topology string

func (t Topology) String() string {
	return string(t)
}

const (
	HabitatStateCreated   HabitatState = "Created"
	HabitatStateProcessed HabitatState = "Processed"

	TopologyStandalone Topology = "standalone"
	TopologyLeader     Topology = "leader"

	HabitatKind = "Habitat"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type HabitatList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []Habitat `json:"items"`
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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 v1beta1

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	HabitatResourcePlural = "habitats"
	HabitatShortName      = "hab"

	// HabitatLabel labels the resources that belong to Habitat.
	// Example: 'habitat: true'
	HabitatLabel = "habitat"
	// HabitatNameLabel contains the user defined Habitat Service name.
	// Example: 'habitat-name: db'
	HabitatNameLabel = "habitat-name"

	TopologyLabel = "topology"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type Habitat struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`
	Spec              HabitatSpec   `json:"spec"`
	Status            HabitatStatus `json:"status,omitempty"`
	// CustomVersion is a field that works around the lack of support for running
	// multiple versions of a CRD.  It encodes the actual version of the type, so
	// that controllers can decide whether to discard an object if the version
	// doesn't match.
	// When absent, it defaults to v1beta1.
	// +optional
	CustomVersion *string `json:"customVersion,omitempty"`
}

type HabitatSpec struct {
	// Count is the amount of Services to start in this Habitat.
	Count int `json:"count"`
	// Image is the Docker image of the Habitat Service.
	Image   string  `json:"image"`
	Service Service `json:"service"`
	// Env is a list of environment variables.
	// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core.
	// Optional.
	Env []corev1.EnvVar `json:"env,omitempty"`
	// +optional
	// V1beta2 are fields for the v1beta2 type.
	V1beta2 *V1beta2 `json:"v1beta2"`
}

// PersistentStorage contains the details of the persistent storage that the
// cluster should provision.
type PersistentStorage struct {
	// Size is the volume's size.
	// It uses the same format as Kubernetes' size fields, e.g. 10Gi
	Size string `json:"size"`
	// MountPath is the path at which the PersistentVolume will be mounted.
	MountPath string `json:"mountPath"`
	// StorageClassName is the name of the StorageClass that the StatefulSet will request.
	StorageClassName string `json:"storageClassName"`
}

// V1beta2 are fields for the v1beta2 type.
type V1beta2 struct {
	// Count is the amount of Services to start in this Habitat.
	Count int `json:"count"`
	// Image is the Docker image of the Habitat Service.
	Image   string  `json:"image"`
	Service Service `json:"service"`
	// Env is a list of environment variables.
	// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core.
	// Optional.
	Env []corev1.EnvVar `json:"env,omitempty"`
	// +optional
	PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"`
}

type HabitatStatus struct {
	State   HabitatState `json:"state,omitempty"`
	Message string       `json:"message,omitempty"`
}

type HabitatState string

type Service struct {
	// Group is the value of the --group flag for the hab client.
	// Optional. Defaults to `default`.
	Group string `json:"group"`
	// Topology is the value of the --topology flag for the hab client.
	Topology `json:"topology"`
	// ConfigSecretName is the name of a Secret containing a Habitat service's config in TOML format.
	// It will be mounted inside the pod as a file, and it will be used by Habitat to configure the service.
	// Optional.
	ConfigSecretName string `json:"configSecretName,omitempty"`
	// The name of the secret that contains the ring key.
	// Optional.
	RingSecretName string `json:"ringSecretName,omitempty"`
	// Bind is when one service connects to another forming a producer/consumer relationship.
	// Optional.
	Bind []Bind `json:"bind,omitempty"`
	// Name is the name of the Habitat service that this Habitat object represents.
	// This field is used to mount the user.toml file in the correct directory under /hab/user/ in the Pod.
	Name string `json:"name"`
}

type Bind struct {
	// Name is the name of the bind specified in the Habitat configuration files.
	Name string `json:"name"`
	// Service is the name of the service this bind refers to.
	Service string `json:"service"`
	// Group is the group of the service this bind refers to.
	Group string `json:"group"`
}

type Topology string

func (t Topology) String() string {
	return string(t)
}

const (
	HabitatStateCreated   HabitatState = "Created"
	HabitatStateProcessed HabitatState = "Processed"

	TopologyStandalone Topology = "standalone"
	TopologyLeader     Topology = "leader"

	HabitatKind = "Habitat"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type HabitatList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []Habitat `json:"items"`
}
<commit_msg>apis/v1beta1: Make comment tag last<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// 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 v1beta1

import (
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	HabitatResourcePlural = "habitats"
	HabitatShortName      = "hab"

	// HabitatLabel labels the resources that belong to Habitat.
	// Example: 'habitat: true'
	HabitatLabel = "habitat"
	// HabitatNameLabel contains the user defined Habitat Service name.
	// Example: 'habitat-name: db'
	HabitatNameLabel = "habitat-name"

	TopologyLabel = "topology"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type Habitat struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata"`
	Spec              HabitatSpec   `json:"spec"`
	Status            HabitatStatus `json:"status,omitempty"`
	// CustomVersion is a field that works around the lack of support for running
	// multiple versions of a CRD.  It encodes the actual version of the type, so
	// that controllers can decide whether to discard an object if the version
	// doesn't match.
	// When absent, it defaults to v1beta1.
	// +optional
	CustomVersion *string `json:"customVersion,omitempty"`
}

type HabitatSpec struct {
	// Count is the amount of Services to start in this Habitat.
	Count int `json:"count"`
	// Image is the Docker image of the Habitat Service.
	Image   string  `json:"image"`
	Service Service `json:"service"`
	// Env is a list of environment variables.
	// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core.
	// Optional.
	Env []corev1.EnvVar `json:"env,omitempty"`
	// V1beta2 are fields for the v1beta2 type.
	// +optional
	V1beta2 *V1beta2 `json:"v1beta2"`
}

// PersistentStorage contains the details of the persistent storage that the
// cluster should provision.
type PersistentStorage struct {
	// Size is the volume's size.
	// It uses the same format as Kubernetes' size fields, e.g. 10Gi
	Size string `json:"size"`
	// MountPath is the path at which the PersistentVolume will be mounted.
	MountPath string `json:"mountPath"`
	// StorageClassName is the name of the StorageClass that the StatefulSet will request.
	StorageClassName string `json:"storageClassName"`
}

// V1beta2 are fields for the v1beta2 type.
type V1beta2 struct {
	// Count is the amount of Services to start in this Habitat.
	Count int `json:"count"`
	// Image is the Docker image of the Habitat Service.
	Image   string  `json:"image"`
	Service Service `json:"service"`
	// Env is a list of environment variables.
	// The EnvVar type is documented at https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.9/#envvar-v1-core.
	// Optional.
	Env []corev1.EnvVar `json:"env,omitempty"`
	// +optional
	PersistentStorage *PersistentStorage `json:"persistentStorage,omitempty"`
}

type HabitatStatus struct {
	State   HabitatState `json:"state,omitempty"`
	Message string       `json:"message,omitempty"`
}

type HabitatState string

type Service struct {
	// Group is the value of the --group flag for the hab client.
	// Optional. Defaults to `default`.
	Group string `json:"group"`
	// Topology is the value of the --topology flag for the hab client.
	Topology `json:"topology"`
	// ConfigSecretName is the name of a Secret containing a Habitat service's config in TOML format.
	// It will be mounted inside the pod as a file, and it will be used by Habitat to configure the service.
	// Optional.
	ConfigSecretName string `json:"configSecretName,omitempty"`
	// The name of the secret that contains the ring key.
	// Optional.
	RingSecretName string `json:"ringSecretName,omitempty"`
	// Bind is when one service connects to another forming a producer/consumer relationship.
	// Optional.
	Bind []Bind `json:"bind,omitempty"`
	// Name is the name of the Habitat service that this Habitat object represents.
	// This field is used to mount the user.toml file in the correct directory under /hab/user/ in the Pod.
	Name string `json:"name"`
}

type Bind struct {
	// Name is the name of the bind specified in the Habitat configuration files.
	Name string `json:"name"`
	// Service is the name of the service this bind refers to.
	Service string `json:"service"`
	// Group is the group of the service this bind refers to.
	Group string `json:"group"`
}

type Topology string

func (t Topology) String() string {
	return string(t)
}

const (
	HabitatStateCreated   HabitatState = "Created"
	HabitatStateProcessed HabitatState = "Processed"

	TopologyStandalone Topology = "standalone"
	TopologyLeader     Topology = "leader"

	HabitatKind = "Habitat"
)

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

type HabitatList struct {
	metav1.TypeMeta `json:",inline"`
	metav1.ListMeta `json:"metadata"`
	Items           []Habitat `json:"items"`
}
<|endoftext|>