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 }