github.phpd.cn/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  }