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