github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/plugin_data.go (about)

     1  /*
     2  Copyright 2020 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"fmt"
    21  	"time"
    22  
    23  	"github.com/gravitational/trace"
    24  )
    25  
    26  // PluginData is used by plugins to store per-resource state.  An instance of PluginData
    27  // corresponds to a resource which may be managed by one or more plugins.  Data is stored
    28  // as a mapping of the form `plugin -> key -> val`, effectively giving each plugin its own
    29  // key-value store.  Importantly, an instance of PluginData can only be created for a resource
    30  // which currently exist, and automatically expires shortly after the corresponding resource.
    31  // Currently, only the AccessRequest resource is supported.
    32  type PluginData interface {
    33  	Resource
    34  	// Entries gets all entries.
    35  	Entries() map[string]*PluginDataEntry
    36  	// Update attempts to apply an update.
    37  	Update(params PluginDataUpdateParams) error
    38  }
    39  
    40  // NewPluginData configures a new PluginData instance associated
    41  // with the supplied resource name (currently, this must be the
    42  // name of an access request).
    43  func NewPluginData(resourceName string, resourceKind string) (PluginData, error) {
    44  	data := PluginDataV3{
    45  		SubKind: resourceKind,
    46  		Metadata: Metadata{
    47  			Name: resourceName,
    48  		},
    49  		Spec: PluginDataSpecV3{
    50  			Entries: make(map[string]*PluginDataEntry),
    51  		},
    52  	}
    53  	if err := data.CheckAndSetDefaults(); err != nil {
    54  		return nil, err
    55  	}
    56  	return &data, nil
    57  }
    58  
    59  // GetKind returns resource kind
    60  func (r *PluginDataV3) GetKind() string {
    61  	return r.Kind
    62  }
    63  
    64  // GetSubKind returns resource subkind
    65  func (r *PluginDataV3) GetSubKind() string {
    66  	return r.SubKind
    67  }
    68  
    69  // SetSubKind sets resource subkind
    70  func (r *PluginDataV3) SetSubKind(subKind string) {
    71  	r.SubKind = subKind
    72  }
    73  
    74  // GetVersion gets resource version
    75  func (r *PluginDataV3) GetVersion() string {
    76  	return r.Version
    77  }
    78  
    79  // GetName gets resource name
    80  func (r *PluginDataV3) GetName() string {
    81  	return r.Metadata.Name
    82  }
    83  
    84  // SetName sets resource name
    85  func (r *PluginDataV3) SetName(name string) {
    86  	r.Metadata.Name = name
    87  }
    88  
    89  // Expiry returns object expiry setting
    90  func (r *PluginDataV3) Expiry() time.Time {
    91  	return r.Metadata.Expiry()
    92  }
    93  
    94  // SetExpiry sets expiry time for the object
    95  func (r *PluginDataV3) SetExpiry(expiry time.Time) {
    96  	r.Metadata.SetExpiry(expiry)
    97  }
    98  
    99  // GetMetadata gets the resource metadata
   100  func (r *PluginDataV3) GetMetadata() Metadata {
   101  	return r.Metadata
   102  }
   103  
   104  // GetResourceID returns resource ID
   105  func (r *PluginDataV3) GetResourceID() int64 {
   106  	return r.Metadata.GetID()
   107  }
   108  
   109  // SetResourceID sets resource ID
   110  func (r *PluginDataV3) SetResourceID(id int64) {
   111  	r.Metadata.SetID(id)
   112  }
   113  
   114  // GetRevision returns the revision
   115  func (r *PluginDataV3) GetRevision() string {
   116  	return r.Metadata.GetRevision()
   117  }
   118  
   119  // SetRevision sets the revision
   120  func (r *PluginDataV3) SetRevision(rev string) {
   121  	r.Metadata.SetRevision(rev)
   122  }
   123  
   124  func (r *PluginDataV3) String() string {
   125  	return fmt.Sprintf("PluginData(kind=%s,resource=%s,entries=%d)", r.GetSubKind(), r.GetName(), len(r.Spec.Entries))
   126  }
   127  
   128  // setStaticFields sets static resource header and metadata fields.
   129  func (r *PluginDataV3) setStaticFields() {
   130  	r.Kind = KindPluginData
   131  	r.Version = V3
   132  }
   133  
   134  // CheckAndSetDefaults checks and sets default values for PluginData.
   135  func (r *PluginDataV3) CheckAndSetDefaults() error {
   136  	r.setStaticFields()
   137  	if err := r.Metadata.CheckAndSetDefaults(); err != nil {
   138  		return trace.Wrap(err)
   139  	}
   140  
   141  	if r.SubKind == "" {
   142  		return trace.BadParameter("plugin data missing subkind")
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  // Entries returns the PluginData entires
   149  func (r *PluginDataV3) Entries() map[string]*PluginDataEntry {
   150  	if r.Spec.Entries == nil {
   151  		r.Spec.Entries = make(map[string]*PluginDataEntry)
   152  	}
   153  	return r.Spec.Entries
   154  }
   155  
   156  // Update updates the PluginData
   157  func (r *PluginDataV3) Update(params PluginDataUpdateParams) error {
   158  	// See #3286 for a complete discussion of the design constraints at play here.
   159  
   160  	if params.Kind != r.GetSubKind() {
   161  		return trace.BadParameter("resource kind mismatch in update params")
   162  	}
   163  
   164  	if params.Resource != r.GetName() {
   165  		return trace.BadParameter("resource name mismatch in update params")
   166  	}
   167  
   168  	// If expectations were given, ensure that they are met before continuing
   169  	if params.Expect != nil {
   170  		if err := r.checkExpectations(params.Plugin, params.Expect); err != nil {
   171  			return trace.Wrap(err)
   172  		}
   173  	}
   174  	// Ensure that Entries has been initialized
   175  	if r.Spec.Entries == nil {
   176  		r.Spec.Entries = make(map[string]*PluginDataEntry, 1)
   177  	}
   178  	// Ensure that the specific Plugin has been initialized
   179  	if r.Spec.Entries[params.Plugin] == nil {
   180  		r.Spec.Entries[params.Plugin] = &PluginDataEntry{
   181  			Data: make(map[string]string, len(params.Set)),
   182  		}
   183  	}
   184  	entry := r.Spec.Entries[params.Plugin]
   185  	for key, val := range params.Set {
   186  		// Keys which are explicitly set to the empty string are
   187  		// treated as DELETE operations.
   188  		if val == "" {
   189  			delete(entry.Data, key)
   190  			continue
   191  		}
   192  		entry.Data[key] = val
   193  	}
   194  	// Its possible that this update was simply clearing all data;
   195  	// if that is the case, remove the entry.
   196  	if len(entry.Data) == 0 {
   197  		delete(r.Spec.Entries, params.Plugin)
   198  	}
   199  	return nil
   200  }
   201  
   202  // checkExpectations verifies that the data for `plugin` matches the expected
   203  // state described by `expect`.  This function implements the behavior of the
   204  // `PluginDataUpdateParams.Expect` mapping.
   205  func (r *PluginDataV3) checkExpectations(plugin string, expect map[string]string) error {
   206  	var entry *PluginDataEntry
   207  	if r.Spec.Entries != nil {
   208  		entry = r.Spec.Entries[plugin]
   209  	}
   210  	if entry == nil {
   211  		// If no entry currently exists, then the only expectation that can
   212  		// match is one which only specifies fields which shouldn't exist.
   213  		for key, val := range expect {
   214  			if val != "" {
   215  				return trace.CompareFailed("expectations not met for field %q", key)
   216  			}
   217  		}
   218  		return nil
   219  	}
   220  	for key, val := range expect {
   221  		if entry.Data[key] != val {
   222  			return trace.CompareFailed("expectations not met for field %q", key)
   223  
   224  		}
   225  	}
   226  	return nil
   227  }
   228  
   229  // Match returns true if the PluginData given matches the filter
   230  func (f *PluginDataFilter) Match(data PluginData) bool {
   231  	if f.Kind != "" && f.Kind != data.GetSubKind() {
   232  		return false
   233  	}
   234  	if f.Resource != "" && f.Resource != data.GetName() {
   235  		return false
   236  	}
   237  	if f.Plugin != "" {
   238  		if _, ok := data.Entries()[f.Plugin]; !ok {
   239  			return false
   240  		}
   241  	}
   242  	return true
   243  }