github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/state/prepared_query.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "regexp" 6 7 "github.com/hashicorp/consul/agent/consul/prepared_query" 8 "github.com/hashicorp/consul/agent/structs" 9 "github.com/hashicorp/go-memdb" 10 ) 11 12 // preparedQueriesTableSchema returns a new table schema used for storing 13 // prepared queries. 14 func preparedQueriesTableSchema() *memdb.TableSchema { 15 return &memdb.TableSchema{ 16 Name: "prepared-queries", 17 Indexes: map[string]*memdb.IndexSchema{ 18 "id": &memdb.IndexSchema{ 19 Name: "id", 20 AllowMissing: false, 21 Unique: true, 22 Indexer: &memdb.UUIDFieldIndex{ 23 Field: "ID", 24 }, 25 }, 26 "name": &memdb.IndexSchema{ 27 Name: "name", 28 AllowMissing: true, 29 Unique: true, 30 Indexer: &memdb.StringFieldIndex{ 31 Field: "Name", 32 Lowercase: true, 33 }, 34 }, 35 "template": &memdb.IndexSchema{ 36 Name: "template", 37 AllowMissing: true, 38 Unique: true, 39 Indexer: &PreparedQueryIndex{}, 40 }, 41 "session": &memdb.IndexSchema{ 42 Name: "session", 43 AllowMissing: true, 44 Unique: false, 45 Indexer: &memdb.UUIDFieldIndex{ 46 Field: "Session", 47 }, 48 }, 49 }, 50 } 51 } 52 53 func init() { 54 registerSchema(preparedQueriesTableSchema) 55 } 56 57 // validUUID is used to check if a given string looks like a UUID 58 var validUUID = regexp.MustCompile(`(?i)^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$`) 59 60 // isUUID returns true if the given string is a valid UUID. 61 func isUUID(str string) bool { 62 const uuidLen = 36 63 if len(str) != uuidLen { 64 return false 65 } 66 67 return validUUID.MatchString(str) 68 } 69 70 // queryWrapper is an internal structure that is used to store a query alongside 71 // its compiled template, which can be nil. 72 type queryWrapper struct { 73 // We embed the PreparedQuery structure so that the UUID field indexer 74 // can see the ID directly. 75 *structs.PreparedQuery 76 77 // ct is the compiled template, or nil if the query isn't a template. The 78 // state store manages this and keeps it up to date every time the query 79 // changes. 80 ct *prepared_query.CompiledTemplate 81 } 82 83 // toPreparedQuery unwraps the internal form of a prepared query and returns 84 // the regular struct. 85 func toPreparedQuery(wrapped interface{}) *structs.PreparedQuery { 86 if wrapped == nil { 87 return nil 88 } 89 return wrapped.(*queryWrapper).PreparedQuery 90 } 91 92 // PreparedQueries is used to pull all the prepared queries from the snapshot. 93 func (s *Snapshot) PreparedQueries() (structs.PreparedQueries, error) { 94 queries, err := s.tx.Get("prepared-queries", "id") 95 if err != nil { 96 return nil, err 97 } 98 99 var ret structs.PreparedQueries 100 for wrapped := queries.Next(); wrapped != nil; wrapped = queries.Next() { 101 ret = append(ret, toPreparedQuery(wrapped)) 102 } 103 return ret, nil 104 } 105 106 // PreparedQuery is used when restoring from a snapshot. For general inserts, 107 // use PreparedQuerySet. 108 func (s *Restore) PreparedQuery(query *structs.PreparedQuery) error { 109 // If this is a template, compile it, otherwise leave the compiled 110 // template field nil. 111 var ct *prepared_query.CompiledTemplate 112 if prepared_query.IsTemplate(query) { 113 var err error 114 ct, err = prepared_query.Compile(query) 115 if err != nil { 116 return fmt.Errorf("failed compiling template: %s", err) 117 } 118 } 119 120 // Insert the wrapped query. 121 if err := s.tx.Insert("prepared-queries", &queryWrapper{query, ct}); err != nil { 122 return fmt.Errorf("failed restoring prepared query: %s", err) 123 } 124 if err := indexUpdateMaxTxn(s.tx, query.ModifyIndex, "prepared-queries"); err != nil { 125 return fmt.Errorf("failed updating index: %s", err) 126 } 127 128 return nil 129 } 130 131 // PreparedQuerySet is used to create or update a prepared query. 132 func (s *Store) PreparedQuerySet(idx uint64, query *structs.PreparedQuery) error { 133 tx := s.db.Txn(true) 134 defer tx.Abort() 135 136 if err := s.preparedQuerySetTxn(tx, idx, query); err != nil { 137 return err 138 } 139 140 tx.Commit() 141 return nil 142 } 143 144 // preparedQuerySetTxn is the inner method used to insert a prepared query with 145 // the proper indexes into the state store. 146 func (s *Store) preparedQuerySetTxn(tx *memdb.Txn, idx uint64, query *structs.PreparedQuery) error { 147 // Check that the ID is set. 148 if query.ID == "" { 149 return ErrMissingQueryID 150 } 151 152 // Check for an existing query. 153 wrapped, err := tx.First("prepared-queries", "id", query.ID) 154 if err != nil { 155 return fmt.Errorf("failed prepared query lookup: %s", err) 156 } 157 existing := toPreparedQuery(wrapped) 158 159 // Set the indexes. 160 if existing != nil { 161 query.CreateIndex = existing.CreateIndex 162 query.ModifyIndex = idx 163 } else { 164 query.CreateIndex = idx 165 query.ModifyIndex = idx 166 } 167 168 // Verify that the query name doesn't already exist, or that we are 169 // updating the same instance that has this name. If this is a template 170 // and the name is empty then we make sure there's not an empty template 171 // already registered. 172 if query.Name != "" { 173 wrapped, err := tx.First("prepared-queries", "name", query.Name) 174 if err != nil { 175 return fmt.Errorf("failed prepared query lookup: %s", err) 176 } 177 other := toPreparedQuery(wrapped) 178 if other != nil && (existing == nil || existing.ID != other.ID) { 179 return fmt.Errorf("name '%s' aliases an existing query name", query.Name) 180 } 181 } else if prepared_query.IsTemplate(query) { 182 wrapped, err := tx.First("prepared-queries", "template", query.Name) 183 if err != nil { 184 return fmt.Errorf("failed prepared query lookup: %s", err) 185 } 186 other := toPreparedQuery(wrapped) 187 if other != nil && (existing == nil || existing.ID != other.ID) { 188 return fmt.Errorf("a query template with an empty name already exists") 189 } 190 } 191 192 // Verify that the name doesn't alias any existing ID. We allow queries 193 // to be looked up by ID *or* name so we don't want anyone to try to 194 // register a query with a name equal to some other query's ID in an 195 // attempt to hijack it. We also look up by ID *then* name in order to 196 // prevent this, but it seems prudent to prevent these types of rogue 197 // queries from ever making it into the state store. Note that we have 198 // to see if the name looks like a UUID before checking since the UUID 199 // index will complain if we look up something that's not formatted 200 // like one. 201 if isUUID(query.Name) { 202 wrapped, err := tx.First("prepared-queries", "id", query.Name) 203 if err != nil { 204 return fmt.Errorf("failed prepared query lookup: %s", err) 205 } 206 if wrapped != nil { 207 return fmt.Errorf("name '%s' aliases an existing query ID", query.Name) 208 } 209 } 210 211 // Verify that the session exists. 212 if query.Session != "" { 213 sess, err := tx.First("sessions", "id", query.Session) 214 if err != nil { 215 return fmt.Errorf("failed session lookup: %s", err) 216 } 217 if sess == nil { 218 return fmt.Errorf("invalid session %#v", query.Session) 219 } 220 } 221 222 // We do not verify the service here, nor the token, if any. These are 223 // checked at execute time and not doing integrity checking on them 224 // helps avoid bootstrapping chicken and egg problems. 225 226 // If this is a template, compile it, otherwise leave the compiled 227 // template field nil. 228 var ct *prepared_query.CompiledTemplate 229 if prepared_query.IsTemplate(query) { 230 var err error 231 ct, err = prepared_query.Compile(query) 232 if err != nil { 233 return fmt.Errorf("failed compiling template: %s", err) 234 } 235 } 236 237 // Insert the wrapped query. 238 if err := tx.Insert("prepared-queries", &queryWrapper{query, ct}); err != nil { 239 return fmt.Errorf("failed inserting prepared query: %s", err) 240 } 241 if err := tx.Insert("index", &IndexEntry{"prepared-queries", idx}); err != nil { 242 return fmt.Errorf("failed updating index: %s", err) 243 } 244 245 return nil 246 } 247 248 // PreparedQueryDelete deletes the given query by ID. 249 func (s *Store) PreparedQueryDelete(idx uint64, queryID string) error { 250 tx := s.db.Txn(true) 251 defer tx.Abort() 252 253 if err := s.preparedQueryDeleteTxn(tx, idx, queryID); err != nil { 254 return fmt.Errorf("failed prepared query delete: %s", err) 255 } 256 257 tx.Commit() 258 return nil 259 } 260 261 // preparedQueryDeleteTxn is the inner method used to delete a prepared query 262 // with the proper indexes into the state store. 263 func (s *Store) preparedQueryDeleteTxn(tx *memdb.Txn, idx uint64, queryID string) error { 264 // Pull the query. 265 wrapped, err := tx.First("prepared-queries", "id", queryID) 266 if err != nil { 267 return fmt.Errorf("failed prepared query lookup: %s", err) 268 } 269 if wrapped == nil { 270 return nil 271 } 272 273 // Delete the query and update the index. 274 if err := tx.Delete("prepared-queries", wrapped); err != nil { 275 return fmt.Errorf("failed prepared query delete: %s", err) 276 } 277 if err := tx.Insert("index", &IndexEntry{"prepared-queries", idx}); err != nil { 278 return fmt.Errorf("failed updating index: %s", err) 279 } 280 281 return nil 282 } 283 284 // PreparedQueryGet returns the given prepared query by ID. 285 func (s *Store) PreparedQueryGet(ws memdb.WatchSet, queryID string) (uint64, *structs.PreparedQuery, error) { 286 tx := s.db.Txn(false) 287 defer tx.Abort() 288 289 // Get the table index. 290 idx := maxIndexTxn(tx, "prepared-queries") 291 292 // Look up the query by its ID. 293 watchCh, wrapped, err := tx.FirstWatch("prepared-queries", "id", queryID) 294 if err != nil { 295 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 296 } 297 ws.Add(watchCh) 298 return idx, toPreparedQuery(wrapped), nil 299 } 300 301 // PreparedQueryResolve returns the given prepared query by looking up an ID or 302 // Name. If the query was looked up by name and it's a template, then the 303 // template will be rendered before it is returned. 304 func (s *Store) PreparedQueryResolve(queryIDOrName string, source structs.QuerySource) (uint64, *structs.PreparedQuery, error) { 305 tx := s.db.Txn(false) 306 defer tx.Abort() 307 308 // Get the table index. 309 idx := maxIndexTxn(tx, "prepared-queries") 310 311 // Explicitly ban an empty query. This will never match an ID and the 312 // schema is set up so it will never match a query with an empty name, 313 // but we check it here to be explicit about it (we'd never want to 314 // return the results from the first query w/o a name). 315 if queryIDOrName == "" { 316 return 0, nil, ErrMissingQueryID 317 } 318 319 // Try first by ID if it looks like they gave us an ID. We check the 320 // format before trying this because the UUID index will complain if 321 // we look up something that's not formatted like one. 322 if isUUID(queryIDOrName) { 323 wrapped, err := tx.First("prepared-queries", "id", queryIDOrName) 324 if err != nil { 325 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 326 } 327 if wrapped != nil { 328 query := toPreparedQuery(wrapped) 329 if prepared_query.IsTemplate(query) { 330 return idx, nil, fmt.Errorf("prepared query templates can only be resolved up by name, not by ID") 331 } 332 return idx, query, nil 333 } 334 } 335 336 // prep will check to see if the query is a template and render it 337 // first, otherwise it will just return a regular query. 338 prep := func(wrapped interface{}) (uint64, *structs.PreparedQuery, error) { 339 wrapper := wrapped.(*queryWrapper) 340 if prepared_query.IsTemplate(wrapper.PreparedQuery) { 341 render, err := wrapper.ct.Render(queryIDOrName, source) 342 if err != nil { 343 return idx, nil, err 344 } 345 return idx, render, nil 346 } 347 return idx, wrapper.PreparedQuery, nil 348 } 349 350 // Next, look for an exact name match. This is the common case for static 351 // prepared queries, and could also apply to templates. 352 { 353 wrapped, err := tx.First("prepared-queries", "name", queryIDOrName) 354 if err != nil { 355 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 356 } 357 if wrapped != nil { 358 return prep(wrapped) 359 } 360 } 361 362 // Next, look for the longest prefix match among the prepared query 363 // templates. 364 { 365 wrapped, err := tx.LongestPrefix("prepared-queries", "template_prefix", queryIDOrName) 366 if err != nil { 367 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 368 } 369 if wrapped != nil { 370 return prep(wrapped) 371 } 372 } 373 374 return idx, nil, nil 375 } 376 377 // PreparedQueryList returns all the prepared queries. 378 func (s *Store) PreparedQueryList(ws memdb.WatchSet) (uint64, structs.PreparedQueries, error) { 379 tx := s.db.Txn(false) 380 defer tx.Abort() 381 382 // Get the table index. 383 idx := maxIndexTxn(tx, "prepared-queries") 384 385 // Query all of the prepared queries in the state store. 386 queries, err := tx.Get("prepared-queries", "id") 387 if err != nil { 388 return 0, nil, fmt.Errorf("failed prepared query lookup: %s", err) 389 } 390 ws.Add(queries.WatchCh()) 391 392 // Go over all of the queries and build the response. 393 var result structs.PreparedQueries 394 for wrapped := queries.Next(); wrapped != nil; wrapped = queries.Next() { 395 result = append(result, toPreparedQuery(wrapped)) 396 } 397 return idx, result, nil 398 }