github.com/janma/nomad@v0.11.3/command/agent/job_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/golang/snappy"
    10  
    11  	"github.com/hashicorp/nomad/api"
    12  	"github.com/hashicorp/nomad/helper"
    13  	"github.com/hashicorp/nomad/jobspec"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  )
    16  
    17  func (s *HTTPServer) JobsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    18  	switch req.Method {
    19  	case "GET":
    20  		return s.jobListRequest(resp, req)
    21  	case "PUT", "POST":
    22  		return s.jobUpdate(resp, req, "")
    23  	default:
    24  		return nil, CodedError(405, ErrInvalidMethod)
    25  	}
    26  }
    27  
    28  func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    29  	args := structs.JobListRequest{}
    30  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
    31  		return nil, nil
    32  	}
    33  
    34  	var out structs.JobListResponse
    35  	if err := s.agent.RPC("Job.List", &args, &out); err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	setMeta(resp, &out.QueryMeta)
    40  	if out.Jobs == nil {
    41  		out.Jobs = make([]*structs.JobListStub, 0)
    42  	}
    43  	return out.Jobs, nil
    44  }
    45  
    46  func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    47  	path := strings.TrimPrefix(req.URL.Path, "/v1/job/")
    48  	switch {
    49  	case strings.HasSuffix(path, "/evaluate"):
    50  		jobName := strings.TrimSuffix(path, "/evaluate")
    51  		return s.jobForceEvaluate(resp, req, jobName)
    52  	case strings.HasSuffix(path, "/allocations"):
    53  		jobName := strings.TrimSuffix(path, "/allocations")
    54  		return s.jobAllocations(resp, req, jobName)
    55  	case strings.HasSuffix(path, "/evaluations"):
    56  		jobName := strings.TrimSuffix(path, "/evaluations")
    57  		return s.jobEvaluations(resp, req, jobName)
    58  	case strings.HasSuffix(path, "/periodic/force"):
    59  		jobName := strings.TrimSuffix(path, "/periodic/force")
    60  		return s.periodicForceRequest(resp, req, jobName)
    61  	case strings.HasSuffix(path, "/plan"):
    62  		jobName := strings.TrimSuffix(path, "/plan")
    63  		return s.jobPlan(resp, req, jobName)
    64  	case strings.HasSuffix(path, "/summary"):
    65  		jobName := strings.TrimSuffix(path, "/summary")
    66  		return s.jobSummaryRequest(resp, req, jobName)
    67  	case strings.HasSuffix(path, "/dispatch"):
    68  		jobName := strings.TrimSuffix(path, "/dispatch")
    69  		return s.jobDispatchRequest(resp, req, jobName)
    70  	case strings.HasSuffix(path, "/versions"):
    71  		jobName := strings.TrimSuffix(path, "/versions")
    72  		return s.jobVersions(resp, req, jobName)
    73  	case strings.HasSuffix(path, "/revert"):
    74  		jobName := strings.TrimSuffix(path, "/revert")
    75  		return s.jobRevert(resp, req, jobName)
    76  	case strings.HasSuffix(path, "/deployments"):
    77  		jobName := strings.TrimSuffix(path, "/deployments")
    78  		return s.jobDeployments(resp, req, jobName)
    79  	case strings.HasSuffix(path, "/deployment"):
    80  		jobName := strings.TrimSuffix(path, "/deployment")
    81  		return s.jobLatestDeployment(resp, req, jobName)
    82  	case strings.HasSuffix(path, "/stable"):
    83  		jobName := strings.TrimSuffix(path, "/stable")
    84  		return s.jobStable(resp, req, jobName)
    85  	case strings.HasSuffix(path, "/scale"):
    86  		jobName := strings.TrimSuffix(path, "/scale")
    87  		return s.jobScale(resp, req, jobName)
    88  	default:
    89  		return s.jobCRUD(resp, req, path)
    90  	}
    91  }
    92  
    93  func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Request,
    94  	jobName string) (interface{}, error) {
    95  	if req.Method != "PUT" && req.Method != "POST" {
    96  		return nil, CodedError(405, ErrInvalidMethod)
    97  	}
    98  	var args structs.JobEvaluateRequest
    99  
   100  	// TODO(preetha): remove in 0.9
   101  	// COMPAT: For backwards compatibility allow using this endpoint without a payload
   102  	if req.ContentLength == 0 {
   103  		args = structs.JobEvaluateRequest{
   104  			JobID: jobName,
   105  		}
   106  	} else {
   107  		if err := decodeBody(req, &args); err != nil {
   108  			return nil, CodedError(400, err.Error())
   109  		}
   110  		if args.JobID == "" {
   111  			return nil, CodedError(400, "Job ID must be specified")
   112  		}
   113  
   114  		if jobName != "" && args.JobID != jobName {
   115  			return nil, CodedError(400, "JobID not same as job name")
   116  		}
   117  	}
   118  	s.parseWriteRequest(req, &args.WriteRequest)
   119  
   120  	var out structs.JobRegisterResponse
   121  	if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil {
   122  		return nil, err
   123  	}
   124  	setIndex(resp, out.Index)
   125  	return out, nil
   126  }
   127  
   128  func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request,
   129  	jobName string) (interface{}, error) {
   130  	if req.Method != "PUT" && req.Method != "POST" {
   131  		return nil, CodedError(405, ErrInvalidMethod)
   132  	}
   133  
   134  	var args api.JobPlanRequest
   135  	if err := decodeBody(req, &args); err != nil {
   136  		return nil, CodedError(400, err.Error())
   137  	}
   138  	if args.Job == nil {
   139  		return nil, CodedError(400, "Job must be specified")
   140  	}
   141  	if args.Job.ID == nil {
   142  		return nil, CodedError(400, "Job must have a valid ID")
   143  	}
   144  	if jobName != "" && *args.Job.ID != jobName {
   145  		return nil, CodedError(400, "Job ID does not match")
   146  	}
   147  
   148  	// Region in http request query param takes precedence over region in job hcl config
   149  	if args.WriteRequest.Region != "" {
   150  		args.Job.Region = helper.StringToPtr(args.WriteRequest.Region)
   151  	}
   152  	// If 'global' region is specified or if no region is given,
   153  	// default to region of the node you're submitting to
   154  	if args.Job.Region == nil || *args.Job.Region == "" || *args.Job.Region == api.GlobalRegion {
   155  		args.Job.Region = &s.agent.config.Region
   156  	}
   157  
   158  	sJob := ApiJobToStructJob(args.Job)
   159  
   160  	planReq := structs.JobPlanRequest{
   161  		Job:            sJob,
   162  		Diff:           args.Diff,
   163  		PolicyOverride: args.PolicyOverride,
   164  		WriteRequest: structs.WriteRequest{
   165  			Region: sJob.Region,
   166  		},
   167  	}
   168  	// parseWriteRequest overrides Namespace, Region and AuthToken
   169  	// based on values from the original http request
   170  	s.parseWriteRequest(req, &planReq.WriteRequest)
   171  	planReq.Namespace = sJob.Namespace
   172  
   173  	var out structs.JobPlanResponse
   174  	if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil {
   175  		return nil, err
   176  	}
   177  	setIndex(resp, out.Index)
   178  	return out, nil
   179  }
   180  
   181  func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   182  	// Ensure request method is POST or PUT
   183  	if !(req.Method == "POST" || req.Method == "PUT") {
   184  		return nil, CodedError(405, ErrInvalidMethod)
   185  	}
   186  
   187  	var validateRequest api.JobValidateRequest
   188  	if err := decodeBody(req, &validateRequest); err != nil {
   189  		return nil, CodedError(400, err.Error())
   190  	}
   191  	if validateRequest.Job == nil {
   192  		return nil, CodedError(400, "Job must be specified")
   193  	}
   194  
   195  	job := ApiJobToStructJob(validateRequest.Job)
   196  
   197  	args := structs.JobValidateRequest{
   198  		Job: job,
   199  		WriteRequest: structs.WriteRequest{
   200  			Region: validateRequest.Region,
   201  		},
   202  	}
   203  	s.parseWriteRequest(req, &args.WriteRequest)
   204  	args.Namespace = job.Namespace
   205  
   206  	var out structs.JobValidateResponse
   207  	if err := s.agent.RPC("Job.Validate", &args, &out); err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	return out, nil
   212  }
   213  
   214  func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request,
   215  	jobName string) (interface{}, error) {
   216  	if req.Method != "PUT" && req.Method != "POST" {
   217  		return nil, CodedError(405, ErrInvalidMethod)
   218  	}
   219  
   220  	args := structs.PeriodicForceRequest{
   221  		JobID: jobName,
   222  	}
   223  	s.parseWriteRequest(req, &args.WriteRequest)
   224  
   225  	var out structs.PeriodicForceResponse
   226  	if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil {
   227  		return nil, err
   228  	}
   229  	setIndex(resp, out.Index)
   230  	return out, nil
   231  }
   232  
   233  func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request,
   234  	jobName string) (interface{}, error) {
   235  	if req.Method != "GET" {
   236  		return nil, CodedError(405, ErrInvalidMethod)
   237  	}
   238  	allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all"))
   239  
   240  	args := structs.JobSpecificRequest{
   241  		JobID: jobName,
   242  		All:   allAllocs,
   243  	}
   244  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   245  		return nil, nil
   246  	}
   247  
   248  	var out structs.JobAllocationsResponse
   249  	if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	setMeta(resp, &out.QueryMeta)
   254  	if out.Allocations == nil {
   255  		out.Allocations = make([]*structs.AllocListStub, 0)
   256  	}
   257  	for _, alloc := range out.Allocations {
   258  		alloc.SetEventDisplayMessages()
   259  	}
   260  	return out.Allocations, nil
   261  }
   262  
   263  func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request,
   264  	jobName string) (interface{}, error) {
   265  	if req.Method != "GET" {
   266  		return nil, CodedError(405, ErrInvalidMethod)
   267  	}
   268  	args := structs.JobSpecificRequest{
   269  		JobID: jobName,
   270  	}
   271  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   272  		return nil, nil
   273  	}
   274  
   275  	var out structs.JobEvaluationsResponse
   276  	if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	setMeta(resp, &out.QueryMeta)
   281  	if out.Evaluations == nil {
   282  		out.Evaluations = make([]*structs.Evaluation, 0)
   283  	}
   284  	return out.Evaluations, nil
   285  }
   286  
   287  func (s *HTTPServer) jobDeployments(resp http.ResponseWriter, req *http.Request,
   288  	jobName string) (interface{}, error) {
   289  	if req.Method != "GET" {
   290  		return nil, CodedError(405, ErrInvalidMethod)
   291  	}
   292  	all, _ := strconv.ParseBool(req.URL.Query().Get("all"))
   293  	args := structs.JobSpecificRequest{
   294  		JobID: jobName,
   295  		All:   all,
   296  	}
   297  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   298  		return nil, nil
   299  	}
   300  
   301  	var out structs.DeploymentListResponse
   302  	if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil {
   303  		return nil, err
   304  	}
   305  
   306  	setMeta(resp, &out.QueryMeta)
   307  	if out.Deployments == nil {
   308  		out.Deployments = make([]*structs.Deployment, 0)
   309  	}
   310  	return out.Deployments, nil
   311  }
   312  
   313  func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request,
   314  	jobName string) (interface{}, error) {
   315  	if req.Method != "GET" {
   316  		return nil, CodedError(405, ErrInvalidMethod)
   317  	}
   318  	args := structs.JobSpecificRequest{
   319  		JobID: jobName,
   320  	}
   321  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   322  		return nil, nil
   323  	}
   324  
   325  	var out structs.SingleDeploymentResponse
   326  	if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil {
   327  		return nil, err
   328  	}
   329  
   330  	setMeta(resp, &out.QueryMeta)
   331  	return out.Deployment, nil
   332  }
   333  
   334  func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request,
   335  	jobName string) (interface{}, error) {
   336  	switch req.Method {
   337  	case "GET":
   338  		return s.jobQuery(resp, req, jobName)
   339  	case "PUT", "POST":
   340  		return s.jobUpdate(resp, req, jobName)
   341  	case "DELETE":
   342  		return s.jobDelete(resp, req, jobName)
   343  	default:
   344  		return nil, CodedError(405, ErrInvalidMethod)
   345  	}
   346  }
   347  
   348  func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request,
   349  	jobName string) (interface{}, error) {
   350  	args := structs.JobSpecificRequest{
   351  		JobID: jobName,
   352  	}
   353  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   354  		return nil, nil
   355  	}
   356  
   357  	var out structs.SingleJobResponse
   358  	if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil {
   359  		return nil, err
   360  	}
   361  
   362  	setMeta(resp, &out.QueryMeta)
   363  	if out.Job == nil {
   364  		return nil, CodedError(404, "job not found")
   365  	}
   366  
   367  	// Decode the payload if there is any
   368  	job := out.Job
   369  	if len(job.Payload) != 0 {
   370  		decoded, err := snappy.Decode(nil, out.Job.Payload)
   371  		if err != nil {
   372  			return nil, err
   373  		}
   374  		job = job.Copy()
   375  		job.Payload = decoded
   376  	}
   377  
   378  	return job, nil
   379  }
   380  
   381  func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request,
   382  	jobName string) (interface{}, error) {
   383  	var args api.JobRegisterRequest
   384  	if err := decodeBody(req, &args); err != nil {
   385  		return nil, CodedError(400, err.Error())
   386  	}
   387  	if args.Job == nil {
   388  		return nil, CodedError(400, "Job must be specified")
   389  	}
   390  
   391  	if args.Job.ID == nil {
   392  		return nil, CodedError(400, "Job ID hasn't been provided")
   393  	}
   394  	if jobName != "" && *args.Job.ID != jobName {
   395  		return nil, CodedError(400, "Job ID does not match name")
   396  	}
   397  
   398  	// Region in http request query param takes precedence over region in job hcl config
   399  	if args.WriteRequest.Region != "" {
   400  		args.Job.Region = helper.StringToPtr(args.WriteRequest.Region)
   401  	}
   402  	// If 'global' region is specified or if no region is given,
   403  	// default to region of the node you're submitting to
   404  	if args.Job.Region == nil || *args.Job.Region == "" || *args.Job.Region == api.GlobalRegion {
   405  		args.Job.Region = &s.agent.config.Region
   406  	}
   407  
   408  	sJob := ApiJobToStructJob(args.Job)
   409  
   410  	regReq := structs.JobRegisterRequest{
   411  		Job:            sJob,
   412  		EnforceIndex:   args.EnforceIndex,
   413  		JobModifyIndex: args.JobModifyIndex,
   414  		PolicyOverride: args.PolicyOverride,
   415  		WriteRequest: structs.WriteRequest{
   416  			Region:    sJob.Region,
   417  			AuthToken: args.WriteRequest.SecretID,
   418  		},
   419  	}
   420  	// parseWriteRequest overrides Namespace, Region and AuthToken
   421  	// based on values from the original http request
   422  	s.parseWriteRequest(req, &regReq.WriteRequest)
   423  	regReq.Namespace = sJob.Namespace
   424  
   425  	var out structs.JobRegisterResponse
   426  	if err := s.agent.RPC("Job.Register", &regReq, &out); err != nil {
   427  		return nil, err
   428  	}
   429  	setIndex(resp, out.Index)
   430  	return out, nil
   431  }
   432  
   433  func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request,
   434  	jobName string) (interface{}, error) {
   435  
   436  	purgeStr := req.URL.Query().Get("purge")
   437  	var purgeBool bool
   438  	if purgeStr != "" {
   439  		var err error
   440  		purgeBool, err = strconv.ParseBool(purgeStr)
   441  		if err != nil {
   442  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err)
   443  		}
   444  	}
   445  
   446  	args := structs.JobDeregisterRequest{
   447  		JobID: jobName,
   448  		Purge: purgeBool,
   449  	}
   450  	s.parseWriteRequest(req, &args.WriteRequest)
   451  
   452  	var out structs.JobDeregisterResponse
   453  	if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil {
   454  		return nil, err
   455  	}
   456  	setIndex(resp, out.Index)
   457  	return out, nil
   458  }
   459  
   460  func (s *HTTPServer) jobScale(resp http.ResponseWriter, req *http.Request,
   461  	jobName string) (interface{}, error) {
   462  
   463  	switch req.Method {
   464  	case "GET":
   465  		return s.jobScaleStatus(resp, req, jobName)
   466  	case "PUT", "POST":
   467  		return s.jobScaleAction(resp, req, jobName)
   468  	default:
   469  		return nil, CodedError(405, ErrInvalidMethod)
   470  	}
   471  }
   472  
   473  func (s *HTTPServer) jobScaleStatus(resp http.ResponseWriter, req *http.Request,
   474  	jobName string) (interface{}, error) {
   475  
   476  	args := structs.JobScaleStatusRequest{
   477  		JobID: jobName,
   478  	}
   479  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   480  		return nil, nil
   481  	}
   482  
   483  	var out structs.JobScaleStatusResponse
   484  	if err := s.agent.RPC("Job.ScaleStatus", &args, &out); err != nil {
   485  		return nil, err
   486  	}
   487  
   488  	setMeta(resp, &out.QueryMeta)
   489  	if out.JobScaleStatus == nil {
   490  		return nil, CodedError(404, "job not found")
   491  	}
   492  
   493  	return out.JobScaleStatus, nil
   494  }
   495  
   496  func (s *HTTPServer) jobScaleAction(resp http.ResponseWriter, req *http.Request,
   497  	jobName string) (interface{}, error) {
   498  
   499  	if req.Method != "PUT" && req.Method != "POST" {
   500  		return nil, CodedError(405, ErrInvalidMethod)
   501  	}
   502  
   503  	var args api.ScalingRequest
   504  	if err := decodeBody(req, &args); err != nil {
   505  		return nil, CodedError(400, err.Error())
   506  	}
   507  
   508  	namespace := args.Target[structs.ScalingTargetNamespace]
   509  	targetJob := args.Target[structs.ScalingTargetJob]
   510  	if targetJob != "" && targetJob != jobName {
   511  		return nil, CodedError(400, "job ID in payload did not match URL")
   512  	}
   513  
   514  	scaleReq := structs.JobScaleRequest{
   515  		JobID:          jobName,
   516  		Namespace:      namespace,
   517  		Target:         args.Target,
   518  		Count:          args.Count,
   519  		PolicyOverride: args.PolicyOverride,
   520  		Message:        args.Message,
   521  		Error:          args.Error,
   522  		Meta:           args.Meta,
   523  	}
   524  	// parseWriteRequest overrides Namespace, Region and AuthToken
   525  	// based on values from the original http request
   526  	s.parseWriteRequest(req, &scaleReq.WriteRequest)
   527  
   528  	var out structs.JobRegisterResponse
   529  	if err := s.agent.RPC("Job.Scale", &scaleReq, &out); err != nil {
   530  		return nil, err
   531  	}
   532  	setIndex(resp, out.Index)
   533  	return out, nil
   534  }
   535  
   536  func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request,
   537  	jobName string) (interface{}, error) {
   538  
   539  	diffsStr := req.URL.Query().Get("diffs")
   540  	var diffsBool bool
   541  	if diffsStr != "" {
   542  		var err error
   543  		diffsBool, err = strconv.ParseBool(diffsStr)
   544  		if err != nil {
   545  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err)
   546  		}
   547  	}
   548  
   549  	args := structs.JobVersionsRequest{
   550  		JobID: jobName,
   551  		Diffs: diffsBool,
   552  	}
   553  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   554  		return nil, nil
   555  	}
   556  
   557  	var out structs.JobVersionsResponse
   558  	if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil {
   559  		return nil, err
   560  	}
   561  
   562  	setMeta(resp, &out.QueryMeta)
   563  	if len(out.Versions) == 0 {
   564  		return nil, CodedError(404, "job versions not found")
   565  	}
   566  
   567  	return out, nil
   568  }
   569  
   570  func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request,
   571  	jobName string) (interface{}, error) {
   572  
   573  	if req.Method != "PUT" && req.Method != "POST" {
   574  		return nil, CodedError(405, ErrInvalidMethod)
   575  	}
   576  
   577  	var revertRequest structs.JobRevertRequest
   578  	if err := decodeBody(req, &revertRequest); err != nil {
   579  		return nil, CodedError(400, err.Error())
   580  	}
   581  	if revertRequest.JobID == "" {
   582  		return nil, CodedError(400, "JobID must be specified")
   583  	}
   584  	if revertRequest.JobID != jobName {
   585  		return nil, CodedError(400, "Job ID does not match")
   586  	}
   587  
   588  	s.parseWriteRequest(req, &revertRequest.WriteRequest)
   589  
   590  	var out structs.JobRegisterResponse
   591  	if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil {
   592  		return nil, err
   593  	}
   594  
   595  	setMeta(resp, &out.QueryMeta)
   596  	return out, nil
   597  }
   598  
   599  func (s *HTTPServer) jobStable(resp http.ResponseWriter, req *http.Request,
   600  	jobName string) (interface{}, error) {
   601  
   602  	if req.Method != "PUT" && req.Method != "POST" {
   603  		return nil, CodedError(405, ErrInvalidMethod)
   604  	}
   605  
   606  	var stableRequest structs.JobStabilityRequest
   607  	if err := decodeBody(req, &stableRequest); err != nil {
   608  		return nil, CodedError(400, err.Error())
   609  	}
   610  	if stableRequest.JobID == "" {
   611  		return nil, CodedError(400, "JobID must be specified")
   612  	}
   613  	if stableRequest.JobID != jobName {
   614  		return nil, CodedError(400, "Job ID does not match")
   615  	}
   616  
   617  	s.parseWriteRequest(req, &stableRequest.WriteRequest)
   618  
   619  	var out structs.JobStabilityResponse
   620  	if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil {
   621  		return nil, err
   622  	}
   623  
   624  	setIndex(resp, out.Index)
   625  	return out, nil
   626  }
   627  
   628  func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   629  	args := structs.JobSummaryRequest{
   630  		JobID: name,
   631  	}
   632  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   633  		return nil, nil
   634  	}
   635  
   636  	var out structs.JobSummaryResponse
   637  	if err := s.agent.RPC("Job.Summary", &args, &out); err != nil {
   638  		return nil, err
   639  	}
   640  
   641  	setMeta(resp, &out.QueryMeta)
   642  	if out.JobSummary == nil {
   643  		return nil, CodedError(404, "job not found")
   644  	}
   645  	setIndex(resp, out.Index)
   646  	return out.JobSummary, nil
   647  }
   648  
   649  func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   650  	if req.Method != "PUT" && req.Method != "POST" {
   651  		return nil, CodedError(405, ErrInvalidMethod)
   652  	}
   653  	args := structs.JobDispatchRequest{}
   654  	if err := decodeBody(req, &args); err != nil {
   655  		return nil, CodedError(400, err.Error())
   656  	}
   657  	if args.JobID != "" && args.JobID != name {
   658  		return nil, CodedError(400, "Job ID does not match")
   659  	}
   660  	if args.JobID == "" {
   661  		args.JobID = name
   662  	}
   663  
   664  	s.parseWriteRequest(req, &args.WriteRequest)
   665  
   666  	var out structs.JobDispatchResponse
   667  	if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil {
   668  		return nil, err
   669  	}
   670  	setIndex(resp, out.Index)
   671  	return out, nil
   672  }
   673  
   674  // JobsParseRequest parses a hcl jobspec and returns a api.Job
   675  func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   676  	if req.Method != http.MethodPut && req.Method != http.MethodPost {
   677  		return nil, CodedError(405, ErrInvalidMethod)
   678  	}
   679  
   680  	args := &api.JobsParseRequest{}
   681  	if err := decodeBody(req, &args); err != nil {
   682  		return nil, CodedError(400, err.Error())
   683  	}
   684  	if args.JobHCL == "" {
   685  		return nil, CodedError(400, "Job spec is empty")
   686  	}
   687  
   688  	jobfile := strings.NewReader(args.JobHCL)
   689  	jobStruct, err := jobspec.Parse(jobfile)
   690  	if err != nil {
   691  		return nil, CodedError(400, err.Error())
   692  	}
   693  
   694  	if args.Canonicalize {
   695  		jobStruct.Canonicalize()
   696  	}
   697  	return jobStruct, nil
   698  }
   699  
   700  func ApiJobToStructJob(job *api.Job) *structs.Job {
   701  	job.Canonicalize()
   702  
   703  	j := &structs.Job{
   704  		Stop:        *job.Stop,
   705  		Region:      *job.Region,
   706  		Namespace:   *job.Namespace,
   707  		ID:          *job.ID,
   708  		ParentID:    *job.ParentID,
   709  		Name:        *job.Name,
   710  		Type:        *job.Type,
   711  		Priority:    *job.Priority,
   712  		AllAtOnce:   *job.AllAtOnce,
   713  		Datacenters: job.Datacenters,
   714  		Payload:     job.Payload,
   715  		Meta:        job.Meta,
   716  		ConsulToken: *job.ConsulToken,
   717  		VaultToken:  *job.VaultToken,
   718  		Constraints: ApiConstraintsToStructs(job.Constraints),
   719  		Affinities:  ApiAffinitiesToStructs(job.Affinities),
   720  	}
   721  
   722  	// Update has been pushed into the task groups. stagger and max_parallel are
   723  	// preserved at the job level, but all other values are discarded. The job.Update
   724  	// api value is merged into TaskGroups already in api.Canonicalize
   725  	if job.Update != nil && job.Update.MaxParallel != nil && *job.Update.MaxParallel > 0 {
   726  		j.Update = structs.UpdateStrategy{}
   727  
   728  		if job.Update.Stagger != nil {
   729  			j.Update.Stagger = *job.Update.Stagger
   730  		}
   731  		if job.Update.MaxParallel != nil {
   732  			j.Update.MaxParallel = *job.Update.MaxParallel
   733  		}
   734  	}
   735  
   736  	if l := len(job.Spreads); l != 0 {
   737  		j.Spreads = make([]*structs.Spread, l)
   738  		for i, apiSpread := range job.Spreads {
   739  			j.Spreads[i] = ApiSpreadToStructs(apiSpread)
   740  		}
   741  	}
   742  
   743  	if job.Periodic != nil {
   744  		j.Periodic = &structs.PeriodicConfig{
   745  			Enabled:         *job.Periodic.Enabled,
   746  			SpecType:        *job.Periodic.SpecType,
   747  			ProhibitOverlap: *job.Periodic.ProhibitOverlap,
   748  			TimeZone:        *job.Periodic.TimeZone,
   749  		}
   750  
   751  		if job.Periodic.Spec != nil {
   752  			j.Periodic.Spec = *job.Periodic.Spec
   753  		}
   754  	}
   755  
   756  	if job.ParameterizedJob != nil {
   757  		j.ParameterizedJob = &structs.ParameterizedJobConfig{
   758  			Payload:      job.ParameterizedJob.Payload,
   759  			MetaRequired: job.ParameterizedJob.MetaRequired,
   760  			MetaOptional: job.ParameterizedJob.MetaOptional,
   761  		}
   762  	}
   763  
   764  	if l := len(job.TaskGroups); l != 0 {
   765  		j.TaskGroups = make([]*structs.TaskGroup, l)
   766  		for i, taskGroup := range job.TaskGroups {
   767  			tg := &structs.TaskGroup{}
   768  			ApiTgToStructsTG(j, taskGroup, tg)
   769  			j.TaskGroups[i] = tg
   770  		}
   771  	}
   772  
   773  	return j
   774  }
   775  
   776  func ApiTgToStructsTG(job *structs.Job, taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
   777  	tg.Name = *taskGroup.Name
   778  	tg.Count = *taskGroup.Count
   779  	tg.Meta = taskGroup.Meta
   780  	tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints)
   781  	tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities)
   782  	tg.Networks = ApiNetworkResourceToStructs(taskGroup.Networks)
   783  	tg.Services = ApiServicesToStructs(taskGroup.Services)
   784  
   785  	tg.RestartPolicy = &structs.RestartPolicy{
   786  		Attempts: *taskGroup.RestartPolicy.Attempts,
   787  		Interval: *taskGroup.RestartPolicy.Interval,
   788  		Delay:    *taskGroup.RestartPolicy.Delay,
   789  		Mode:     *taskGroup.RestartPolicy.Mode,
   790  	}
   791  
   792  	if taskGroup.ShutdownDelay != nil {
   793  		tg.ShutdownDelay = taskGroup.ShutdownDelay
   794  	}
   795  
   796  	if taskGroup.StopAfterClientDisconnect != nil {
   797  		tg.StopAfterClientDisconnect = taskGroup.StopAfterClientDisconnect
   798  	}
   799  
   800  	if taskGroup.ReschedulePolicy != nil {
   801  		tg.ReschedulePolicy = &structs.ReschedulePolicy{
   802  			Attempts:      *taskGroup.ReschedulePolicy.Attempts,
   803  			Interval:      *taskGroup.ReschedulePolicy.Interval,
   804  			Delay:         *taskGroup.ReschedulePolicy.Delay,
   805  			DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction,
   806  			MaxDelay:      *taskGroup.ReschedulePolicy.MaxDelay,
   807  			Unlimited:     *taskGroup.ReschedulePolicy.Unlimited,
   808  		}
   809  	}
   810  
   811  	if taskGroup.Migrate != nil {
   812  		tg.Migrate = &structs.MigrateStrategy{
   813  			MaxParallel:     *taskGroup.Migrate.MaxParallel,
   814  			HealthCheck:     *taskGroup.Migrate.HealthCheck,
   815  			MinHealthyTime:  *taskGroup.Migrate.MinHealthyTime,
   816  			HealthyDeadline: *taskGroup.Migrate.HealthyDeadline,
   817  		}
   818  	}
   819  
   820  	if taskGroup.Scaling != nil {
   821  		tg.Scaling = ApiScalingPolicyToStructs(tg.Count, taskGroup.Scaling).TargetTaskGroup(job, tg)
   822  	}
   823  
   824  	tg.EphemeralDisk = &structs.EphemeralDisk{
   825  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   826  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   827  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   828  	}
   829  
   830  	if l := len(taskGroup.Spreads); l != 0 {
   831  		tg.Spreads = make([]*structs.Spread, l)
   832  		for k, spread := range taskGroup.Spreads {
   833  			tg.Spreads[k] = ApiSpreadToStructs(spread)
   834  		}
   835  	}
   836  
   837  	if l := len(taskGroup.Volumes); l != 0 {
   838  		tg.Volumes = make(map[string]*structs.VolumeRequest, l)
   839  		for k, v := range taskGroup.Volumes {
   840  			if v.Type != structs.VolumeTypeHost && v.Type != structs.VolumeTypeCSI {
   841  				// Ignore volumes we don't understand in this iteration currently.
   842  				// - This is because we don't currently have a way to return errors here.
   843  				continue
   844  			}
   845  
   846  			vol := &structs.VolumeRequest{
   847  				Name:     v.Name,
   848  				Type:     v.Type,
   849  				ReadOnly: v.ReadOnly,
   850  				Source:   v.Source,
   851  			}
   852  
   853  			if v.MountOptions != nil {
   854  				vol.MountOptions = &structs.CSIMountOptions{
   855  					FSType:     v.MountOptions.FSType,
   856  					MountFlags: v.MountOptions.MountFlags,
   857  				}
   858  			}
   859  
   860  			tg.Volumes[k] = vol
   861  		}
   862  	}
   863  
   864  	if taskGroup.Update != nil {
   865  		tg.Update = &structs.UpdateStrategy{
   866  			Stagger:          *taskGroup.Update.Stagger,
   867  			MaxParallel:      *taskGroup.Update.MaxParallel,
   868  			HealthCheck:      *taskGroup.Update.HealthCheck,
   869  			MinHealthyTime:   *taskGroup.Update.MinHealthyTime,
   870  			HealthyDeadline:  *taskGroup.Update.HealthyDeadline,
   871  			ProgressDeadline: *taskGroup.Update.ProgressDeadline,
   872  			Canary:           *taskGroup.Update.Canary,
   873  		}
   874  
   875  		// boolPtr fields may be nil, others will have pointers to default values via Canonicalize
   876  		if taskGroup.Update.AutoRevert != nil {
   877  			tg.Update.AutoRevert = *taskGroup.Update.AutoRevert
   878  		}
   879  
   880  		if taskGroup.Update.AutoPromote != nil {
   881  			tg.Update.AutoPromote = *taskGroup.Update.AutoPromote
   882  		}
   883  	}
   884  
   885  	if l := len(taskGroup.Tasks); l != 0 {
   886  		tg.Tasks = make([]*structs.Task, l)
   887  		for l, task := range taskGroup.Tasks {
   888  			t := &structs.Task{}
   889  			ApiTaskToStructsTask(task, t)
   890  			tg.Tasks[l] = t
   891  		}
   892  	}
   893  }
   894  
   895  // ApiTaskToStructsTask is a copy and type conversion between the API
   896  // representation of a task from a struct representation of a task.
   897  func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
   898  	structsTask.Name = apiTask.Name
   899  	structsTask.Driver = apiTask.Driver
   900  	structsTask.User = apiTask.User
   901  	structsTask.Leader = apiTask.Leader
   902  	structsTask.Config = apiTask.Config
   903  	structsTask.Env = apiTask.Env
   904  	structsTask.Meta = apiTask.Meta
   905  	structsTask.KillTimeout = *apiTask.KillTimeout
   906  	structsTask.ShutdownDelay = apiTask.ShutdownDelay
   907  	structsTask.KillSignal = apiTask.KillSignal
   908  	structsTask.Kind = structs.TaskKind(apiTask.Kind)
   909  	structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints)
   910  	structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities)
   911  	structsTask.CSIPluginConfig = ApiCSIPluginConfigToStructsCSIPluginConfig(apiTask.CSIPluginConfig)
   912  
   913  	if apiTask.RestartPolicy != nil {
   914  		structsTask.RestartPolicy = &structs.RestartPolicy{
   915  			Attempts: *apiTask.RestartPolicy.Attempts,
   916  			Interval: *apiTask.RestartPolicy.Interval,
   917  			Delay:    *apiTask.RestartPolicy.Delay,
   918  			Mode:     *apiTask.RestartPolicy.Mode,
   919  		}
   920  	}
   921  
   922  	if l := len(apiTask.VolumeMounts); l != 0 {
   923  		structsTask.VolumeMounts = make([]*structs.VolumeMount, l)
   924  		for i, mount := range apiTask.VolumeMounts {
   925  			structsTask.VolumeMounts[i] = &structs.VolumeMount{
   926  				Volume:          *mount.Volume,
   927  				Destination:     *mount.Destination,
   928  				ReadOnly:        *mount.ReadOnly,
   929  				PropagationMode: *mount.PropagationMode,
   930  			}
   931  		}
   932  	}
   933  
   934  	if l := len(apiTask.Services); l != 0 {
   935  		structsTask.Services = make([]*structs.Service, l)
   936  		for i, service := range apiTask.Services {
   937  			structsTask.Services[i] = &structs.Service{
   938  				Name:              service.Name,
   939  				PortLabel:         service.PortLabel,
   940  				Tags:              service.Tags,
   941  				CanaryTags:        service.CanaryTags,
   942  				EnableTagOverride: service.EnableTagOverride,
   943  				AddressMode:       service.AddressMode,
   944  				Meta:              helper.CopyMapStringString(service.Meta),
   945  				CanaryMeta:        helper.CopyMapStringString(service.CanaryMeta),
   946  			}
   947  
   948  			if l := len(service.Checks); l != 0 {
   949  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   950  				for j, check := range service.Checks {
   951  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   952  						Name:          check.Name,
   953  						Type:          check.Type,
   954  						Command:       check.Command,
   955  						Args:          check.Args,
   956  						Path:          check.Path,
   957  						Protocol:      check.Protocol,
   958  						PortLabel:     check.PortLabel,
   959  						AddressMode:   check.AddressMode,
   960  						Interval:      check.Interval,
   961  						Timeout:       check.Timeout,
   962  						InitialStatus: check.InitialStatus,
   963  						TLSSkipVerify: check.TLSSkipVerify,
   964  						Header:        check.Header,
   965  						Method:        check.Method,
   966  						GRPCService:   check.GRPCService,
   967  						GRPCUseTLS:    check.GRPCUseTLS,
   968  					}
   969  					if check.CheckRestart != nil {
   970  						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
   971  							Limit:          check.CheckRestart.Limit,
   972  							Grace:          *check.CheckRestart.Grace,
   973  							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
   974  						}
   975  					}
   976  				}
   977  			}
   978  		}
   979  	}
   980  
   981  	structsTask.Resources = ApiResourcesToStructs(apiTask.Resources)
   982  
   983  	structsTask.LogConfig = &structs.LogConfig{
   984  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   985  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   986  	}
   987  
   988  	if l := len(apiTask.Artifacts); l != 0 {
   989  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   990  		for k, ta := range apiTask.Artifacts {
   991  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   992  				GetterSource:  *ta.GetterSource,
   993  				GetterOptions: ta.GetterOptions,
   994  				GetterMode:    *ta.GetterMode,
   995  				RelativeDest:  *ta.RelativeDest,
   996  			}
   997  		}
   998  	}
   999  
  1000  	if apiTask.Vault != nil {
  1001  		structsTask.Vault = &structs.Vault{
  1002  			Policies:     apiTask.Vault.Policies,
  1003  			Env:          *apiTask.Vault.Env,
  1004  			ChangeMode:   *apiTask.Vault.ChangeMode,
  1005  			ChangeSignal: *apiTask.Vault.ChangeSignal,
  1006  		}
  1007  	}
  1008  
  1009  	if l := len(apiTask.Templates); l != 0 {
  1010  		structsTask.Templates = make([]*structs.Template, l)
  1011  		for i, template := range apiTask.Templates {
  1012  			structsTask.Templates[i] = &structs.Template{
  1013  				SourcePath:   *template.SourcePath,
  1014  				DestPath:     *template.DestPath,
  1015  				EmbeddedTmpl: *template.EmbeddedTmpl,
  1016  				ChangeMode:   *template.ChangeMode,
  1017  				ChangeSignal: *template.ChangeSignal,
  1018  				Splay:        *template.Splay,
  1019  				Perms:        *template.Perms,
  1020  				LeftDelim:    *template.LeftDelim,
  1021  				RightDelim:   *template.RightDelim,
  1022  				Envvars:      *template.Envvars,
  1023  				VaultGrace:   *template.VaultGrace,
  1024  			}
  1025  		}
  1026  	}
  1027  
  1028  	if apiTask.DispatchPayload != nil {
  1029  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
  1030  			File: apiTask.DispatchPayload.File,
  1031  		}
  1032  	}
  1033  
  1034  	if apiTask.Lifecycle != nil {
  1035  		structsTask.Lifecycle = &structs.TaskLifecycleConfig{
  1036  			Hook:    apiTask.Lifecycle.Hook,
  1037  			Sidecar: apiTask.Lifecycle.Sidecar,
  1038  		}
  1039  	}
  1040  }
  1041  
  1042  func ApiCSIPluginConfigToStructsCSIPluginConfig(apiConfig *api.TaskCSIPluginConfig) *structs.TaskCSIPluginConfig {
  1043  	if apiConfig == nil {
  1044  		return nil
  1045  	}
  1046  
  1047  	sc := &structs.TaskCSIPluginConfig{}
  1048  	sc.ID = apiConfig.ID
  1049  	sc.Type = structs.CSIPluginType(apiConfig.Type)
  1050  	sc.MountDir = apiConfig.MountDir
  1051  	return sc
  1052  }
  1053  
  1054  func ApiResourcesToStructs(in *api.Resources) *structs.Resources {
  1055  	if in == nil {
  1056  		return nil
  1057  	}
  1058  
  1059  	out := &structs.Resources{
  1060  		CPU:      *in.CPU,
  1061  		MemoryMB: *in.MemoryMB,
  1062  	}
  1063  
  1064  	// COMPAT(0.10): Only being used to issue warnings
  1065  	if in.IOPS != nil {
  1066  		out.IOPS = *in.IOPS
  1067  	}
  1068  
  1069  	if len(in.Networks) != 0 {
  1070  		out.Networks = ApiNetworkResourceToStructs(in.Networks)
  1071  	}
  1072  
  1073  	if l := len(in.Devices); l != 0 {
  1074  		out.Devices = make([]*structs.RequestedDevice, l)
  1075  		for i, d := range in.Devices {
  1076  			out.Devices[i] = &structs.RequestedDevice{
  1077  				Name:        d.Name,
  1078  				Count:       *d.Count,
  1079  				Constraints: ApiConstraintsToStructs(d.Constraints),
  1080  				Affinities:  ApiAffinitiesToStructs(d.Affinities),
  1081  			}
  1082  		}
  1083  	}
  1084  
  1085  	return out
  1086  }
  1087  
  1088  func ApiNetworkResourceToStructs(in []*api.NetworkResource) []*structs.NetworkResource {
  1089  	var out []*structs.NetworkResource
  1090  	if len(in) == 0 {
  1091  		return out
  1092  	}
  1093  	out = make([]*structs.NetworkResource, len(in))
  1094  	for i, nw := range in {
  1095  		out[i] = &structs.NetworkResource{
  1096  			Mode:  nw.Mode,
  1097  			CIDR:  nw.CIDR,
  1098  			IP:    nw.IP,
  1099  			MBits: *nw.MBits,
  1100  		}
  1101  
  1102  		if l := len(nw.DynamicPorts); l != 0 {
  1103  			out[i].DynamicPorts = make([]structs.Port, l)
  1104  			for j, dp := range nw.DynamicPorts {
  1105  				out[i].DynamicPorts[j] = structs.Port{
  1106  					Label: dp.Label,
  1107  					Value: dp.Value,
  1108  					To:    dp.To,
  1109  				}
  1110  			}
  1111  		}
  1112  
  1113  		if l := len(nw.ReservedPorts); l != 0 {
  1114  			out[i].ReservedPorts = make([]structs.Port, l)
  1115  			for j, rp := range nw.ReservedPorts {
  1116  				out[i].ReservedPorts[j] = structs.Port{
  1117  					Label: rp.Label,
  1118  					Value: rp.Value,
  1119  					To:    rp.To,
  1120  				}
  1121  			}
  1122  		}
  1123  	}
  1124  
  1125  	return out
  1126  }
  1127  
  1128  //TODO(schmichael) refactor and reuse in service parsing above
  1129  func ApiServicesToStructs(in []*api.Service) []*structs.Service {
  1130  	if len(in) == 0 {
  1131  		return nil
  1132  	}
  1133  
  1134  	out := make([]*structs.Service, len(in))
  1135  	for i, s := range in {
  1136  		out[i] = &structs.Service{
  1137  			Name:              s.Name,
  1138  			PortLabel:         s.PortLabel,
  1139  			Tags:              s.Tags,
  1140  			CanaryTags:        s.CanaryTags,
  1141  			EnableTagOverride: s.EnableTagOverride,
  1142  			AddressMode:       s.AddressMode,
  1143  			Meta:              helper.CopyMapStringString(s.Meta),
  1144  			CanaryMeta:        helper.CopyMapStringString(s.CanaryMeta),
  1145  		}
  1146  
  1147  		if l := len(s.Checks); l != 0 {
  1148  			out[i].Checks = make([]*structs.ServiceCheck, l)
  1149  			for j, check := range s.Checks {
  1150  				out[i].Checks[j] = &structs.ServiceCheck{
  1151  					Name:          check.Name,
  1152  					Type:          check.Type,
  1153  					Command:       check.Command,
  1154  					Args:          check.Args,
  1155  					Path:          check.Path,
  1156  					Protocol:      check.Protocol,
  1157  					PortLabel:     check.PortLabel,
  1158  					Expose:        check.Expose,
  1159  					AddressMode:   check.AddressMode,
  1160  					Interval:      check.Interval,
  1161  					Timeout:       check.Timeout,
  1162  					InitialStatus: check.InitialStatus,
  1163  					TLSSkipVerify: check.TLSSkipVerify,
  1164  					Header:        check.Header,
  1165  					Method:        check.Method,
  1166  					GRPCService:   check.GRPCService,
  1167  					GRPCUseTLS:    check.GRPCUseTLS,
  1168  					TaskName:      check.TaskName,
  1169  				}
  1170  				if check.CheckRestart != nil {
  1171  					out[i].Checks[j].CheckRestart = &structs.CheckRestart{
  1172  						Limit:          check.CheckRestart.Limit,
  1173  						Grace:          *check.CheckRestart.Grace,
  1174  						IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
  1175  					}
  1176  				}
  1177  			}
  1178  		}
  1179  
  1180  		if s.Connect != nil {
  1181  			out[i].Connect = ApiConsulConnectToStructs(s.Connect)
  1182  		}
  1183  
  1184  	}
  1185  
  1186  	return out
  1187  }
  1188  
  1189  func ApiConsulConnectToStructs(in *api.ConsulConnect) *structs.ConsulConnect {
  1190  	if in == nil {
  1191  		return nil
  1192  	}
  1193  	return &structs.ConsulConnect{
  1194  		Native:         in.Native,
  1195  		SidecarService: apiConnectSidecarServiceToStructs(in.SidecarService),
  1196  		SidecarTask:    apiConnectSidecarTaskToStructs(in.SidecarTask),
  1197  	}
  1198  }
  1199  
  1200  func apiConnectSidecarServiceToStructs(in *api.ConsulSidecarService) *structs.ConsulSidecarService {
  1201  	if in == nil {
  1202  		return nil
  1203  	}
  1204  	return &structs.ConsulSidecarService{
  1205  		Port:  in.Port,
  1206  		Tags:  helper.CopySliceString(in.Tags),
  1207  		Proxy: apiConnectSidecarServiceProxyToStructs(in.Proxy),
  1208  	}
  1209  }
  1210  
  1211  func apiConnectSidecarServiceProxyToStructs(in *api.ConsulProxy) *structs.ConsulProxy {
  1212  	if in == nil {
  1213  		return nil
  1214  	}
  1215  	return &structs.ConsulProxy{
  1216  		LocalServiceAddress: in.LocalServiceAddress,
  1217  		LocalServicePort:    in.LocalServicePort,
  1218  		Upstreams:           apiUpstreamsToStructs(in.Upstreams),
  1219  		Expose:              apiConsulExposeConfigToStructs(in.ExposeConfig),
  1220  		Config:              in.Config,
  1221  	}
  1222  }
  1223  
  1224  func apiUpstreamsToStructs(in []*api.ConsulUpstream) []structs.ConsulUpstream {
  1225  	if len(in) == 0 {
  1226  		return nil
  1227  	}
  1228  	upstreams := make([]structs.ConsulUpstream, len(in))
  1229  	for i, upstream := range in {
  1230  		upstreams[i] = structs.ConsulUpstream{
  1231  			DestinationName: upstream.DestinationName,
  1232  			LocalBindPort:   upstream.LocalBindPort,
  1233  		}
  1234  	}
  1235  	return upstreams
  1236  }
  1237  
  1238  func apiConsulExposeConfigToStructs(in *api.ConsulExposeConfig) *structs.ConsulExposeConfig {
  1239  	if in == nil {
  1240  		return nil
  1241  	}
  1242  	return &structs.ConsulExposeConfig{
  1243  		Paths: apiConsulExposePathsToStructs(in.Path),
  1244  	}
  1245  }
  1246  
  1247  func apiConsulExposePathsToStructs(in []*api.ConsulExposePath) []structs.ConsulExposePath {
  1248  	if len(in) == 0 {
  1249  		return nil
  1250  	}
  1251  	paths := make([]structs.ConsulExposePath, len(in))
  1252  	for i, path := range in {
  1253  		paths[i] = structs.ConsulExposePath{
  1254  			Path:          path.Path,
  1255  			Protocol:      path.Protocol,
  1256  			LocalPathPort: path.LocalPathPort,
  1257  			ListenerPort:  path.ListenerPort,
  1258  		}
  1259  	}
  1260  	return paths
  1261  }
  1262  
  1263  func apiConnectSidecarTaskToStructs(in *api.SidecarTask) *structs.SidecarTask {
  1264  	if in == nil {
  1265  		return nil
  1266  	}
  1267  	return &structs.SidecarTask{
  1268  		Name:          in.Name,
  1269  		Driver:        in.Driver,
  1270  		User:          in.User,
  1271  		Config:        in.Config,
  1272  		Env:           in.Env,
  1273  		Resources:     ApiResourcesToStructs(in.Resources),
  1274  		Meta:          in.Meta,
  1275  		ShutdownDelay: in.ShutdownDelay,
  1276  		KillSignal:    in.KillSignal,
  1277  		KillTimeout:   in.KillTimeout,
  1278  		LogConfig:     apiLogConfigToStructs(in.LogConfig),
  1279  	}
  1280  }
  1281  
  1282  func apiLogConfigToStructs(in *api.LogConfig) *structs.LogConfig {
  1283  	if in == nil {
  1284  		return nil
  1285  	}
  1286  	return &structs.LogConfig{
  1287  		MaxFiles:      dereferenceInt(in.MaxFiles),
  1288  		MaxFileSizeMB: dereferenceInt(in.MaxFileSizeMB),
  1289  	}
  1290  }
  1291  
  1292  func dereferenceInt(in *int) int {
  1293  	if in == nil {
  1294  		return 0
  1295  	}
  1296  	return *in
  1297  }
  1298  
  1299  func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint {
  1300  	if in == nil {
  1301  		return nil
  1302  	}
  1303  
  1304  	out := make([]*structs.Constraint, len(in))
  1305  	for i, ac := range in {
  1306  		out[i] = ApiConstraintToStructs(ac)
  1307  	}
  1308  
  1309  	return out
  1310  }
  1311  
  1312  func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint {
  1313  	if in == nil {
  1314  		return nil
  1315  	}
  1316  
  1317  	return &structs.Constraint{
  1318  		LTarget: in.LTarget,
  1319  		RTarget: in.RTarget,
  1320  		Operand: in.Operand,
  1321  	}
  1322  }
  1323  
  1324  func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity {
  1325  	if in == nil {
  1326  		return nil
  1327  	}
  1328  
  1329  	out := make([]*structs.Affinity, len(in))
  1330  	for i, ac := range in {
  1331  		out[i] = ApiAffinityToStructs(ac)
  1332  	}
  1333  
  1334  	return out
  1335  }
  1336  
  1337  func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity {
  1338  	return &structs.Affinity{
  1339  		LTarget: a1.LTarget,
  1340  		Operand: a1.Operand,
  1341  		RTarget: a1.RTarget,
  1342  		Weight:  *a1.Weight,
  1343  	}
  1344  }
  1345  
  1346  func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread {
  1347  	ret := &structs.Spread{}
  1348  	ret.Attribute = a1.Attribute
  1349  	ret.Weight = *a1.Weight
  1350  	if a1.SpreadTarget != nil {
  1351  		ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget))
  1352  		for i, st := range a1.SpreadTarget {
  1353  			ret.SpreadTarget[i] = &structs.SpreadTarget{
  1354  				Value:   st.Value,
  1355  				Percent: st.Percent,
  1356  			}
  1357  		}
  1358  	}
  1359  	return ret
  1360  }