github.com/kjdelisle/consul@v1.4.5/agent/prepared_query_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  
     9  	cachetype "github.com/hashicorp/consul/agent/cache-types"
    10  	"github.com/hashicorp/consul/agent/consul"
    11  	"github.com/hashicorp/consul/agent/structs"
    12  )
    13  
    14  // preparedQueryCreateResponse is used to wrap the query ID.
    15  type preparedQueryCreateResponse struct {
    16  	ID string
    17  }
    18  
    19  // preparedQueryCreate makes a new prepared query.
    20  func (s *HTTPServer) preparedQueryCreate(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    21  	args := structs.PreparedQueryRequest{
    22  		Op: structs.PreparedQueryCreate,
    23  	}
    24  	s.parseDC(req, &args.Datacenter)
    25  	s.parseToken(req, &args.Token)
    26  	if err := decodeBody(req, &args.Query, nil); err != nil {
    27  		resp.WriteHeader(http.StatusBadRequest)
    28  		fmt.Fprintf(resp, "Request decode failed: %v", err)
    29  		return nil, nil
    30  	}
    31  
    32  	var reply string
    33  	if err := s.agent.RPC("PreparedQuery.Apply", &args, &reply); err != nil {
    34  		return nil, err
    35  	}
    36  	return preparedQueryCreateResponse{reply}, nil
    37  }
    38  
    39  // preparedQueryList returns all the prepared queries.
    40  func (s *HTTPServer) preparedQueryList(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    41  	var args structs.DCSpecificRequest
    42  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    43  		return nil, nil
    44  	}
    45  
    46  	var reply structs.IndexedPreparedQueries
    47  	defer setMeta(resp, &reply.QueryMeta)
    48  RETRY_ONCE:
    49  	if err := s.agent.RPC("PreparedQuery.List", &args, &reply); err != nil {
    50  		return nil, err
    51  	}
    52  	if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact {
    53  		args.AllowStale = false
    54  		args.MaxStaleDuration = 0
    55  		goto RETRY_ONCE
    56  	}
    57  	reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
    58  
    59  	// Use empty list instead of nil.
    60  	if reply.Queries == nil {
    61  		reply.Queries = make(structs.PreparedQueries, 0)
    62  	}
    63  	return reply.Queries, nil
    64  }
    65  
    66  // PreparedQueryGeneral handles all the general prepared query requests.
    67  func (s *HTTPServer) PreparedQueryGeneral(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    68  	switch req.Method {
    69  	case "POST":
    70  		return s.preparedQueryCreate(resp, req)
    71  
    72  	case "GET":
    73  		return s.preparedQueryList(resp, req)
    74  
    75  	default:
    76  		return nil, MethodNotAllowedError{req.Method, []string{"GET", "POST"}}
    77  	}
    78  }
    79  
    80  // parseLimit parses the optional limit parameter for a prepared query execution.
    81  func parseLimit(req *http.Request, limit *int) error {
    82  	*limit = 0
    83  	if arg := req.URL.Query().Get("limit"); arg != "" {
    84  		i, err := strconv.Atoi(arg)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		*limit = i
    89  	}
    90  	return nil
    91  }
    92  
    93  // preparedQueryExecute executes a prepared query.
    94  func (s *HTTPServer) preparedQueryExecute(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    95  	args := structs.PreparedQueryExecuteRequest{
    96  		QueryIDOrName: id,
    97  		Agent: structs.QuerySource{
    98  			Node:       s.agent.config.NodeName,
    99  			Datacenter: s.agent.config.Datacenter,
   100  			Segment:    s.agent.config.SegmentName,
   101  		},
   102  	}
   103  	s.parseSource(req, &args.Source)
   104  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   105  		return nil, nil
   106  	}
   107  	if err := parseLimit(req, &args.Limit); err != nil {
   108  		return nil, fmt.Errorf("Bad limit: %s", err)
   109  	}
   110  
   111  	params := req.URL.Query()
   112  	if raw := params.Get("connect"); raw != "" {
   113  		val, err := strconv.ParseBool(raw)
   114  		if err != nil {
   115  			return nil, fmt.Errorf("Error parsing 'connect' value: %s", err)
   116  		}
   117  
   118  		args.Connect = val
   119  	}
   120  
   121  	var reply structs.PreparedQueryExecuteResponse
   122  	defer setMeta(resp, &reply.QueryMeta)
   123  
   124  	if args.QueryOptions.UseCache {
   125  		raw, m, err := s.agent.cache.Get(cachetype.PreparedQueryName, &args)
   126  		if err != nil {
   127  			// Don't return error if StaleIfError is set and we are within it and had
   128  			// a cached value.
   129  			if raw != nil && m.Hit && args.QueryOptions.StaleIfError > m.Age {
   130  				// Fall through to the happy path below
   131  			} else {
   132  				return nil, err
   133  			}
   134  		}
   135  		defer setCacheMeta(resp, &m)
   136  		r, ok := raw.(*structs.PreparedQueryExecuteResponse)
   137  		if !ok {
   138  			// This should never happen, but we want to protect against panics
   139  			return nil, fmt.Errorf("internal error: response type not correct")
   140  		}
   141  		reply = *r
   142  	} else {
   143  	RETRY_ONCE:
   144  		if err := s.agent.RPC("PreparedQuery.Execute", &args, &reply); err != nil {
   145  			// We have to check the string since the RPC sheds
   146  			// the specific error type.
   147  			if err.Error() == consul.ErrQueryNotFound.Error() {
   148  				resp.WriteHeader(http.StatusNotFound)
   149  				fmt.Fprint(resp, err.Error())
   150  				return nil, nil
   151  			}
   152  			return nil, err
   153  		}
   154  		if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact {
   155  			args.AllowStale = false
   156  			args.MaxStaleDuration = 0
   157  			goto RETRY_ONCE
   158  		}
   159  	}
   160  	reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
   161  
   162  	// Note that we translate using the DC that the results came from, since
   163  	// a query can fail over to a different DC than where the execute request
   164  	// was sent to. That's why we use the reply's DC and not the one from
   165  	// the args.
   166  	s.agent.TranslateAddresses(reply.Datacenter, reply.Nodes)
   167  
   168  	// Use empty list instead of nil.
   169  	if reply.Nodes == nil {
   170  		reply.Nodes = make(structs.CheckServiceNodes, 0)
   171  	}
   172  	return reply, nil
   173  }
   174  
   175  // preparedQueryExplain shows which query a name resolves to, the fully
   176  // interpolated template (if it's a template), as well as additional info
   177  // about the execution of a query.
   178  func (s *HTTPServer) preparedQueryExplain(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   179  	args := structs.PreparedQueryExecuteRequest{
   180  		QueryIDOrName: id,
   181  		Agent: structs.QuerySource{
   182  			Node:       s.agent.config.NodeName,
   183  			Datacenter: s.agent.config.Datacenter,
   184  			Segment:    s.agent.config.SegmentName,
   185  		},
   186  	}
   187  	s.parseSource(req, &args.Source)
   188  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   189  		return nil, nil
   190  	}
   191  	if err := parseLimit(req, &args.Limit); err != nil {
   192  		return nil, fmt.Errorf("Bad limit: %s", err)
   193  	}
   194  
   195  	var reply structs.PreparedQueryExplainResponse
   196  	defer setMeta(resp, &reply.QueryMeta)
   197  RETRY_ONCE:
   198  	if err := s.agent.RPC("PreparedQuery.Explain", &args, &reply); err != nil {
   199  		// We have to check the string since the RPC sheds
   200  		// the specific error type.
   201  		if err.Error() == consul.ErrQueryNotFound.Error() {
   202  			resp.WriteHeader(http.StatusNotFound)
   203  			fmt.Fprint(resp, err.Error())
   204  			return nil, nil
   205  		}
   206  		return nil, err
   207  	}
   208  	if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact {
   209  		args.AllowStale = false
   210  		args.MaxStaleDuration = 0
   211  		goto RETRY_ONCE
   212  	}
   213  	reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
   214  	return reply, nil
   215  }
   216  
   217  // preparedQueryGet returns a single prepared query.
   218  func (s *HTTPServer) preparedQueryGet(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   219  	args := structs.PreparedQuerySpecificRequest{
   220  		QueryID: id,
   221  	}
   222  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
   223  		return nil, nil
   224  	}
   225  
   226  	var reply structs.IndexedPreparedQueries
   227  	defer setMeta(resp, &reply.QueryMeta)
   228  RETRY_ONCE:
   229  	if err := s.agent.RPC("PreparedQuery.Get", &args, &reply); err != nil {
   230  		// We have to check the string since the RPC sheds
   231  		// the specific error type.
   232  		if err.Error() == consul.ErrQueryNotFound.Error() {
   233  			resp.WriteHeader(http.StatusNotFound)
   234  			fmt.Fprint(resp, err.Error())
   235  			return nil, nil
   236  		}
   237  		return nil, err
   238  	}
   239  	if args.QueryOptions.AllowStale && args.MaxStaleDuration > 0 && args.MaxStaleDuration < reply.LastContact {
   240  		args.AllowStale = false
   241  		args.MaxStaleDuration = 0
   242  		goto RETRY_ONCE
   243  	}
   244  	reply.ConsistencyLevel = args.QueryOptions.ConsistencyLevel()
   245  	return reply.Queries, nil
   246  }
   247  
   248  // preparedQueryUpdate updates a prepared query.
   249  func (s *HTTPServer) preparedQueryUpdate(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   250  	args := structs.PreparedQueryRequest{
   251  		Op: structs.PreparedQueryUpdate,
   252  	}
   253  	s.parseDC(req, &args.Datacenter)
   254  	s.parseToken(req, &args.Token)
   255  	if req.ContentLength > 0 {
   256  		if err := decodeBody(req, &args.Query, nil); err != nil {
   257  			resp.WriteHeader(http.StatusBadRequest)
   258  			fmt.Fprintf(resp, "Request decode failed: %v", err)
   259  			return nil, nil
   260  		}
   261  	}
   262  
   263  	if args.Query == nil {
   264  		args.Query = &structs.PreparedQuery{}
   265  	}
   266  
   267  	// Take the ID from the URL, not the embedded one.
   268  	args.Query.ID = id
   269  
   270  	var reply string
   271  	if err := s.agent.RPC("PreparedQuery.Apply", &args, &reply); err != nil {
   272  		return nil, err
   273  	}
   274  	return nil, nil
   275  }
   276  
   277  // preparedQueryDelete deletes prepared query.
   278  func (s *HTTPServer) preparedQueryDelete(id string, resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   279  	args := structs.PreparedQueryRequest{
   280  		Op: structs.PreparedQueryDelete,
   281  		Query: &structs.PreparedQuery{
   282  			ID: id,
   283  		},
   284  	}
   285  	s.parseDC(req, &args.Datacenter)
   286  	s.parseToken(req, &args.Token)
   287  
   288  	var reply string
   289  	if err := s.agent.RPC("PreparedQuery.Apply", &args, &reply); err != nil {
   290  		return nil, err
   291  	}
   292  	return nil, nil
   293  }
   294  
   295  // PreparedQuerySpecificOptions handles OPTIONS requests to prepared query endpoints.
   296  func (s *HTTPServer) preparedQuerySpecificOptions(resp http.ResponseWriter, req *http.Request) interface{} {
   297  	path := req.URL.Path
   298  	switch {
   299  	case strings.HasSuffix(path, "/execute"):
   300  		resp.Header().Add("Allow", strings.Join([]string{"OPTIONS", "GET"}, ","))
   301  		return resp
   302  
   303  	case strings.HasSuffix(path, "/explain"):
   304  		resp.Header().Add("Allow", strings.Join([]string{"OPTIONS", "GET"}, ","))
   305  		return resp
   306  
   307  	default:
   308  		resp.Header().Add("Allow", strings.Join([]string{"OPTIONS", "GET", "PUT", "DELETE"}, ","))
   309  		return resp
   310  	}
   311  }
   312  
   313  // PreparedQuerySpecific handles all the prepared query requests specific to a
   314  // particular query.
   315  func (s *HTTPServer) PreparedQuerySpecific(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   316  	if req.Method == "OPTIONS" {
   317  		return s.preparedQuerySpecificOptions(resp, req), nil
   318  	}
   319  
   320  	path := req.URL.Path
   321  	id := strings.TrimPrefix(path, "/v1/query/")
   322  
   323  	switch {
   324  	case strings.HasSuffix(path, "/execute"):
   325  		if req.Method != "GET" {
   326  			return nil, MethodNotAllowedError{req.Method, []string{"GET"}}
   327  		}
   328  		id = strings.TrimSuffix(id, "/execute")
   329  		return s.preparedQueryExecute(id, resp, req)
   330  
   331  	case strings.HasSuffix(path, "/explain"):
   332  		if req.Method != "GET" {
   333  			return nil, MethodNotAllowedError{req.Method, []string{"GET"}}
   334  		}
   335  		id = strings.TrimSuffix(id, "/explain")
   336  		return s.preparedQueryExplain(id, resp, req)
   337  
   338  	default:
   339  		switch req.Method {
   340  		case "GET":
   341  			return s.preparedQueryGet(id, resp, req)
   342  
   343  		case "PUT":
   344  			return s.preparedQueryUpdate(id, resp, req)
   345  
   346  		case "DELETE":
   347  			return s.preparedQueryDelete(id, resp, req)
   348  
   349  		default:
   350  			return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
   351  		}
   352  	}
   353  }