mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 02:03:24 +00:00
92ed2db38a
Mostly cosmetic changes: - making receiver names consistent - renaming `foo.FooBar` to `foo.Bar` (using package name as prefix) - removing redundant `else` branches - changing `a += 1` to `a++`
628 lines
18 KiB
Go
628 lines
18 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// SemaphoreKindConnection is the semaphore kind used by
|
|
// the Concurrent Session Control feature to limit concurrent
|
|
// connections (corresponds to the `max_connections`
|
|
// role option).
|
|
const SemaphoreKindConnection = "connection"
|
|
|
|
// Semaphores provides ability to control
|
|
// how many shared resources of some kind are acquired at the same time,
|
|
// used to implement concurrent sessions control in a distributed environment
|
|
type Semaphores interface {
|
|
// AcquireSemaphore acquires lease with requested resources from semaphore
|
|
AcquireSemaphore(ctx context.Context, params AcquireSemaphoreRequest) (*SemaphoreLease, error)
|
|
// KeepAliveSemaphoreLease updates semaphore lease
|
|
KeepAliveSemaphoreLease(ctx context.Context, lease SemaphoreLease) error
|
|
// CancelSemaphoreLease cancels semaphore lease early
|
|
CancelSemaphoreLease(ctx context.Context, lease SemaphoreLease) error
|
|
// GetSemaphores returns a list of semaphores matching supplied filter.
|
|
GetSemaphores(ctx context.Context, filter SemaphoreFilter) ([]Semaphore, error)
|
|
// DeleteSemaphore deletes a semaphore matching supplied filter.
|
|
DeleteSemaphore(ctx context.Context, filter SemaphoreFilter) error
|
|
}
|
|
|
|
// Match checks if the supplied semaphore matches this filter.
|
|
func (f *SemaphoreFilter) Match(sem Semaphore) bool {
|
|
if f.SemaphoreKind != "" && f.SemaphoreKind != sem.GetSubKind() {
|
|
return false
|
|
}
|
|
if f.SemaphoreName != "" && f.SemaphoreName != sem.GetName() {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type SemaphoreLockConfig struct {
|
|
// Service is the service against which all semaphore
|
|
// operations are performed.
|
|
Service Semaphores
|
|
// Expiry is an optional lease expiry parameter.
|
|
Expiry time.Duration
|
|
// TickRate is the rate at which lease renewals are attempted
|
|
// and defaults to 1/2 expiry. Used to accelerate tests.
|
|
TickRate time.Duration
|
|
// Params holds the semaphore lease acquisition parameters.
|
|
Params AcquireSemaphoreRequest
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default parameters
|
|
func (l *SemaphoreLockConfig) CheckAndSetDefaults() error {
|
|
if l.Service == nil {
|
|
return trace.BadParameter("missing semaphore service")
|
|
}
|
|
if l.Expiry == 0 {
|
|
l.Expiry = defaults.SessionControlTimeout
|
|
}
|
|
if l.Expiry < time.Millisecond {
|
|
return trace.BadParameter("sub-millisecond lease expiry is not supported: %v", l.Expiry)
|
|
}
|
|
if l.TickRate == 0 {
|
|
l.TickRate = l.Expiry / 2
|
|
}
|
|
if l.TickRate >= l.Expiry {
|
|
return trace.BadParameter("tick-rate must be less than expiry")
|
|
}
|
|
if l.Params.Expires.IsZero() {
|
|
l.Params.Expires = time.Now().UTC().Add(l.Expiry)
|
|
}
|
|
if err := l.Params.Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SemaphoreLock provides a convenient interface for managing
|
|
// semaphore lease keepalive operations.
|
|
type SemaphoreLock struct {
|
|
cfg SemaphoreLockConfig
|
|
lease0 SemaphoreLease
|
|
retry utils.Retry
|
|
ticker *time.Ticker
|
|
doneC chan struct{}
|
|
closeOnce sync.Once
|
|
renewalC chan struct{}
|
|
cond *sync.Cond
|
|
err error
|
|
fin bool
|
|
}
|
|
|
|
// finish registers the final result of the background
|
|
// goroutine. must be called even if err is nil in
|
|
// order to wake any goroutines waiting on the error
|
|
// and mark the lock as finished.
|
|
func (l *SemaphoreLock) finish(err error) {
|
|
l.cond.L.Lock()
|
|
defer l.cond.L.Unlock()
|
|
l.err = err
|
|
l.fin = true
|
|
l.cond.Broadcast()
|
|
}
|
|
|
|
// Done signals that lease keepalive operations
|
|
// have stopped.
|
|
func (l *SemaphoreLock) Done() <-chan struct{} {
|
|
return l.doneC
|
|
}
|
|
|
|
// Wait blocks until the final result is available. Note that
|
|
// this method may block longer than desired since cancellation of
|
|
// the parent context triggers the *start* of the release operation.
|
|
func (l *SemaphoreLock) Wait() error {
|
|
l.cond.L.Lock()
|
|
defer l.cond.L.Unlock()
|
|
for !l.fin {
|
|
l.cond.Wait()
|
|
}
|
|
return l.err
|
|
}
|
|
|
|
// Stop stops associated lease keepalive.
|
|
func (l *SemaphoreLock) Stop() {
|
|
l.closeOnce.Do(func() {
|
|
l.ticker.Stop()
|
|
close(l.doneC)
|
|
})
|
|
}
|
|
|
|
// Renewed notifies on next successful lease keepalive.
|
|
// Used in tests to block until next renewal.
|
|
func (l *SemaphoreLock) Renewed() <-chan struct{} {
|
|
return l.renewalC
|
|
}
|
|
|
|
func (l *SemaphoreLock) KeepAlive(ctx context.Context) {
|
|
var nodrop bool
|
|
var err error
|
|
lease := l.lease0
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer func() {
|
|
cancel()
|
|
l.Stop()
|
|
defer l.finish(err)
|
|
if nodrop {
|
|
// non-standard exit conditions; don't bother handling
|
|
// cancellation/expiry.
|
|
return
|
|
}
|
|
if lease.Expires.After(time.Now().UTC()) {
|
|
// parent context is closed. create orphan context with generous
|
|
// timeout for lease cancellation scope. this will not block any
|
|
// caller that is not explicitly waiting on the final error value.
|
|
cancelContext, cancel := context.WithTimeout(context.Background(), l.cfg.Expiry/4)
|
|
defer cancel()
|
|
err = l.cfg.Service.CancelSemaphoreLease(cancelContext, lease)
|
|
if err != nil {
|
|
log.Warnf("Failed to cancel semaphore lease %s/%s: %v", lease.SemaphoreKind, lease.SemaphoreName, err)
|
|
}
|
|
} else {
|
|
log.Errorf("Semaphore lease expired: %s/%s", lease.SemaphoreKind, lease.SemaphoreName)
|
|
}
|
|
}()
|
|
Outer:
|
|
for {
|
|
select {
|
|
case tick := <-l.ticker.C:
|
|
leaseContext, leaseCancel := context.WithDeadline(ctx, lease.Expires)
|
|
nextLease := lease
|
|
nextLease.Expires = tick.Add(l.cfg.Expiry)
|
|
for {
|
|
err = l.cfg.Service.KeepAliveSemaphoreLease(leaseContext, nextLease)
|
|
if trace.IsNotFound(err) {
|
|
leaseCancel()
|
|
// semaphore and/or lease no longer exist; best to log the error
|
|
// and exit immediately.
|
|
log.Warnf("Halting keepalive on semaphore %s/%s early: %v", lease.SemaphoreKind, lease.SemaphoreName, err)
|
|
nodrop = true
|
|
return
|
|
}
|
|
if err == nil {
|
|
leaseCancel()
|
|
lease = nextLease
|
|
l.retry.Reset()
|
|
select {
|
|
case l.renewalC <- struct{}{}:
|
|
default:
|
|
}
|
|
continue Outer
|
|
}
|
|
log.Debugf("Failed to renew semaphore lease %s/%s: %v", lease.SemaphoreKind, lease.SemaphoreName, err)
|
|
l.retry.Inc()
|
|
select {
|
|
case <-l.retry.After():
|
|
case <-leaseContext.Done():
|
|
leaseCancel() // demanded by linter
|
|
return
|
|
case <-l.Done():
|
|
leaseCancel()
|
|
return
|
|
}
|
|
}
|
|
case <-ctx.Done():
|
|
return
|
|
case <-l.Done():
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// AcquireSemaphoreLock attempts to acquire and hold a semaphore lease. If successfully acquired,
|
|
// background keepalive processes are started and an associated lock handle is returned. Cancelling
|
|
// the supplied context releases the semaphore.
|
|
func AcquireSemaphoreLock(ctx context.Context, cfg SemaphoreLockConfig) (*SemaphoreLock, error) {
|
|
if err := cfg.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// set up retry with a ratio which will result in 3-4 retries before the lease expires
|
|
retry, err := utils.NewLinear(utils.LinearConfig{
|
|
Max: cfg.Expiry / 4,
|
|
Step: cfg.Expiry / 16,
|
|
Jitter: utils.NewJitter(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
lease, err := cfg.Service.AcquireSemaphore(ctx, cfg.Params)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
lock := &SemaphoreLock{
|
|
cfg: cfg,
|
|
lease0: *lease,
|
|
retry: retry,
|
|
ticker: time.NewTicker(cfg.TickRate),
|
|
doneC: make(chan struct{}),
|
|
renewalC: make(chan struct{}),
|
|
cond: sync.NewCond(&sync.Mutex{}),
|
|
}
|
|
return lock, nil
|
|
}
|
|
|
|
// Check verifies that all required parameters have been supplied.
|
|
func (s *AcquireSemaphoreRequest) Check() error {
|
|
if s.SemaphoreKind == "" {
|
|
return trace.BadParameter("missing parameter SemaphoreKind")
|
|
}
|
|
if s.SemaphoreName == "" {
|
|
return trace.BadParameter("missing parameter SemaphoreName")
|
|
}
|
|
if s.MaxLeases == 0 {
|
|
return trace.BadParameter("missing parameter MaxLeases")
|
|
}
|
|
if s.Expires.IsZero() {
|
|
return trace.BadParameter("missing parameter Expires")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ConfigureSemaphore configures an empty semaphore resource matching
|
|
// these acquire parameters.
|
|
func (s *AcquireSemaphoreRequest) ConfigureSemaphore() (Semaphore, error) {
|
|
sem := SemaphoreV3{
|
|
Kind: KindSemaphore,
|
|
SubKind: s.SemaphoreKind,
|
|
Version: V3,
|
|
Metadata: Metadata{
|
|
Name: s.SemaphoreName,
|
|
Namespace: defaults.Namespace,
|
|
},
|
|
}
|
|
sem.SetExpiry(s.Expires)
|
|
if err := sem.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &sem, nil
|
|
}
|
|
|
|
// Semaphore represents distributed semaphore concept
|
|
type Semaphore interface {
|
|
// Resource contains common resource values
|
|
Resource
|
|
// CheckAndSetDefaults checks and sets default parameters
|
|
CheckAndSetDefaults() error
|
|
// Contains checks if lease is member of this semaphore.
|
|
Contains(lease SemaphoreLease) bool
|
|
// Acquire attempts to acquire a lease with this semaphore.
|
|
Acquire(leaseID string, params AcquireSemaphoreRequest) (*SemaphoreLease, error)
|
|
// KeepAlive attempts to update the expiry of an existent lease.
|
|
KeepAlive(lease SemaphoreLease) error
|
|
// Cancel attempts to cancel an existent lease.
|
|
Cancel(lease SemaphoreLease) error
|
|
// LeaseRefs grants access to the underlying list
|
|
// of lease references.
|
|
LeaseRefs() []SemaphoreLeaseRef
|
|
// RemoveExpiredLeases removes expired leases
|
|
RemoveExpiredLeases(now time.Time)
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default values
|
|
func (l *SemaphoreLease) CheckAndSetDefaults() error {
|
|
if l.SemaphoreKind == "" {
|
|
return trace.BadParameter("missing parameter SemaphoreKind")
|
|
}
|
|
if l.SemaphoreName == "" {
|
|
return trace.BadParameter("missing parameter SemaphoreName")
|
|
}
|
|
if l.LeaseID == "" {
|
|
return trace.BadParameter("missing parameter LeaseID")
|
|
}
|
|
if l.Expires.IsZero() {
|
|
return trace.BadParameter("missing lease expiry time")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Contains checks if lease is member of this semaphore.
|
|
func (c *SemaphoreV3) Contains(lease SemaphoreLease) bool {
|
|
if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
|
|
return false
|
|
}
|
|
for _, ref := range c.Spec.Leases {
|
|
if ref.LeaseID == lease.LeaseID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Acquire attempts to acquire a lease with this semaphore.
|
|
func (c *SemaphoreV3) Acquire(leaseID string, params AcquireSemaphoreRequest) (*SemaphoreLease, error) {
|
|
if params.SemaphoreKind != c.GetSubKind() || params.SemaphoreName != c.GetName() {
|
|
return nil, trace.BadParameter("cannot acquire, params do not match")
|
|
}
|
|
|
|
if c.leaseCount() >= params.MaxLeases {
|
|
return nil, trace.LimitExceeded("cannot acquire semaphore %s/%s (%s)",
|
|
c.GetSubKind(),
|
|
c.GetName(),
|
|
teleport.MaxLeases,
|
|
)
|
|
}
|
|
|
|
for _, ref := range c.Spec.Leases {
|
|
if ref.LeaseID == leaseID {
|
|
return nil, trace.AlreadyExists("semaphore lease already exists: %q", leaseID)
|
|
}
|
|
}
|
|
|
|
if params.Expires.After(c.Expiry()) {
|
|
c.SetExpiry(params.Expires)
|
|
}
|
|
|
|
c.Spec.Leases = append(c.Spec.Leases, SemaphoreLeaseRef{
|
|
LeaseID: leaseID,
|
|
Expires: params.Expires,
|
|
Holder: params.Holder,
|
|
})
|
|
|
|
return &SemaphoreLease{
|
|
SemaphoreKind: params.SemaphoreKind,
|
|
SemaphoreName: params.SemaphoreName,
|
|
LeaseID: leaseID,
|
|
Expires: params.Expires,
|
|
}, nil
|
|
}
|
|
|
|
// KeepAlive attempts to update the expiry of an existent lease.
|
|
func (c *SemaphoreV3) KeepAlive(lease SemaphoreLease) error {
|
|
if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
|
|
return trace.BadParameter("cannot keepalive, lease does not match")
|
|
}
|
|
for i := range c.Spec.Leases {
|
|
if c.Spec.Leases[i].LeaseID == lease.LeaseID {
|
|
c.Spec.Leases[i].Expires = lease.Expires
|
|
if lease.Expires.After(c.Expiry()) {
|
|
c.SetExpiry(lease.Expires)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return trace.NotFound("cannot keepalive, lease not found: %q", lease.LeaseID)
|
|
}
|
|
|
|
// Cancel attempts to cancel an existent lease.
|
|
func (c *SemaphoreV3) Cancel(lease SemaphoreLease) error {
|
|
if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
|
|
return trace.BadParameter("cannot cancel, lease does not match")
|
|
}
|
|
for i, ref := range c.Spec.Leases {
|
|
if ref.LeaseID == lease.LeaseID {
|
|
c.Spec.Leases = append(c.Spec.Leases[:i], c.Spec.Leases[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
return trace.NotFound("cannot cancel, lease not found: %q", lease.LeaseID)
|
|
}
|
|
|
|
// RemoveExpiredLeases removes expired leases
|
|
func (c *SemaphoreV3) RemoveExpiredLeases(now time.Time) {
|
|
// See https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
|
|
filtered := c.Spec.Leases[:0]
|
|
for _, lease := range c.Spec.Leases {
|
|
if lease.Expires.After(now) {
|
|
filtered = append(filtered, lease)
|
|
}
|
|
}
|
|
c.Spec.Leases = filtered
|
|
}
|
|
|
|
// leaseCount returns the number of active leases
|
|
func (c *SemaphoreV3) leaseCount() int64 {
|
|
return int64(len(c.Spec.Leases))
|
|
}
|
|
|
|
// LeaseRefs grants access to the underlying list
|
|
// of lease references
|
|
func (c *SemaphoreV3) LeaseRefs() []SemaphoreLeaseRef {
|
|
return c.Spec.Leases
|
|
}
|
|
|
|
// GetVersion returns resource version
|
|
func (c *SemaphoreV3) GetVersion() string {
|
|
return c.Version
|
|
}
|
|
|
|
// GetSubKind returns resource subkind
|
|
func (c *SemaphoreV3) GetSubKind() string {
|
|
return c.SubKind
|
|
}
|
|
|
|
// SetSubKind sets resource subkind
|
|
func (c *SemaphoreV3) SetSubKind(sk string) {
|
|
c.SubKind = sk
|
|
}
|
|
|
|
// GetKind returns resource kind
|
|
func (c *SemaphoreV3) GetKind() string {
|
|
return c.Kind
|
|
}
|
|
|
|
// GetResourceID returns resource ID
|
|
func (c *SemaphoreV3) GetResourceID() int64 {
|
|
return c.Metadata.ID
|
|
}
|
|
|
|
// SetResourceID sets resource ID
|
|
func (c *SemaphoreV3) SetResourceID(id int64) {
|
|
c.Metadata.ID = id
|
|
}
|
|
|
|
// GetName returns the name of the cluster.
|
|
func (c *SemaphoreV3) GetName() string {
|
|
return c.Metadata.Name
|
|
}
|
|
|
|
// SetName sets the name of the cluster.
|
|
func (c *SemaphoreV3) SetName(e string) {
|
|
c.Metadata.Name = e
|
|
}
|
|
|
|
// Expires returns object expiry setting
|
|
func (c *SemaphoreV3) Expiry() time.Time {
|
|
return c.Metadata.Expiry()
|
|
}
|
|
|
|
// SetExpiry sets expiry time for the object
|
|
func (c *SemaphoreV3) SetExpiry(expires time.Time) {
|
|
c.Metadata.SetExpiry(expires)
|
|
}
|
|
|
|
// SetTTL sets Expires header using realtime clock
|
|
func (c *SemaphoreV3) SetTTL(clock clockwork.Clock, ttl time.Duration) {
|
|
c.Metadata.SetTTL(clock, ttl)
|
|
}
|
|
|
|
// GetMetadata returns object metadata
|
|
func (c *SemaphoreV3) GetMetadata() Metadata {
|
|
return c.Metadata
|
|
}
|
|
|
|
// String represents a human readable version of the semaphore.
|
|
func (c *SemaphoreV3) String() string {
|
|
return fmt.Sprintf("Semaphore(kind=%v, name=%v, leases=%v)",
|
|
c.SubKind, c.Metadata.Name, c.leaseCount())
|
|
}
|
|
|
|
// CheckAndSetDefaults checks validity of all parameters and sets defaults.
|
|
func (c *SemaphoreV3) CheckAndSetDefaults() error {
|
|
// make sure we have defaults for all metadata fields
|
|
err := c.Metadata.CheckAndSetDefaults()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// While theoretically there are scenarios with non-expiring semaphores
|
|
// however the flow don't need them right now, and they add a lot of edge
|
|
// cases, so the code does not support them.
|
|
if c.Expiry().IsZero() {
|
|
return trace.BadParameter("set semaphore expiry time")
|
|
}
|
|
if c.SubKind == "" {
|
|
return trace.BadParameter("supply semaphore SubKind parameter")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SemaphoreSpecSchemaTemplate is a template for Semaphore schema.
|
|
const SemaphoreSpecSchemaTemplate = `{
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"leases": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"lease_id": { "type": "string" },
|
|
"expires": { "type": "string" },
|
|
"holder": { "type": "string" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
// GetSemaphoreSchema returns the validation schema for this object
|
|
func GetSemaphoreSchema() string {
|
|
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, SemaphoreSpecSchemaTemplate, DefaultDefinitions)
|
|
}
|
|
|
|
// SemaphoreMarshaler implements marshal/unmarshal of Semaphore implementations
|
|
// mostly adds support for extended versions.
|
|
type SemaphoreMarshaler interface {
|
|
Marshal(c Semaphore, opts ...MarshalOption) ([]byte, error)
|
|
Unmarshal(bytes []byte, opts ...MarshalOption) (Semaphore, error)
|
|
}
|
|
|
|
var semaphoreMarshaler SemaphoreMarshaler = &TeleportSemaphoreMarshaler{}
|
|
|
|
// SetSemaphoreMarshaler sets the marshaler.
|
|
func SetSemaphoreMarshaler(m SemaphoreMarshaler) {
|
|
marshalerMutex.Lock()
|
|
defer marshalerMutex.Unlock()
|
|
semaphoreMarshaler = m
|
|
}
|
|
|
|
// GetSemaphoreMarshaler gets the marshaler.
|
|
func GetSemaphoreMarshaler() SemaphoreMarshaler {
|
|
marshalerMutex.RLock()
|
|
defer marshalerMutex.RUnlock()
|
|
return semaphoreMarshaler
|
|
}
|
|
|
|
// TeleportSemaphoreMarshaler is used to marshal and unmarshal Semaphore.
|
|
type TeleportSemaphoreMarshaler struct{}
|
|
|
|
// Unmarshal unmarshals Semaphore from JSON.
|
|
func (t *TeleportSemaphoreMarshaler) Unmarshal(bytes []byte, opts ...MarshalOption) (Semaphore, error) {
|
|
var semaphore SemaphoreV3
|
|
|
|
if len(bytes) == 0 {
|
|
return nil, trace.BadParameter("missing resource data")
|
|
}
|
|
|
|
cfg, err := collectOptions(opts)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if cfg.SkipValidation {
|
|
if err := utils.FastUnmarshal(bytes, &semaphore); err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
} else {
|
|
err = utils.UnmarshalWithSchema(GetSemaphoreSchema(), &semaphore, bytes)
|
|
if err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
}
|
|
|
|
err = semaphore.CheckAndSetDefaults()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if cfg.ID != 0 {
|
|
semaphore.SetResourceID(cfg.ID)
|
|
}
|
|
if !cfg.Expires.IsZero() {
|
|
semaphore.SetExpiry(cfg.Expires)
|
|
}
|
|
return &semaphore, nil
|
|
}
|
|
|
|
// Marshal marshals Semaphore to JSON.
|
|
func (t *TeleportSemaphoreMarshaler) Marshal(c Semaphore, opts ...MarshalOption) ([]byte, error) {
|
|
cfg, err := collectOptions(opts)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
switch resource := c.(type) {
|
|
case *SemaphoreV3:
|
|
if !cfg.PreserveResourceID {
|
|
// avoid modifying the original object
|
|
// to prevent unexpected data races
|
|
copy := *resource
|
|
copy.SetResourceID(0)
|
|
resource = ©
|
|
}
|
|
return utils.FastMarshal(resource)
|
|
default:
|
|
return nil, trace.BadParameter("unrecognized resource version %T", c)
|
|
}
|
|
}
|