github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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/jobspec"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  func (s *HTTPServer) JobsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    16  	switch req.Method {
    17  	case "GET":
    18  		return s.jobListRequest(resp, req)
    19  	case "PUT", "POST":
    20  		return s.jobUpdate(resp, req, "")
    21  	default:
    22  		return nil, CodedError(405, ErrInvalidMethod)
    23  	}
    24  }
    25  
    26  func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    27  	args := structs.JobListRequest{}
    28  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
    29  		return nil, nil
    30  	}
    31  
    32  	var out structs.JobListResponse
    33  	if err := s.agent.RPC("Job.List", &args, &out); err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	setMeta(resp, &out.QueryMeta)
    38  	if out.Jobs == nil {
    39  		out.Jobs = make([]*structs.JobListStub, 0)
    40  	}
    41  	return out.Jobs, nil
    42  }
    43  
    44  func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    45  	path := strings.TrimPrefix(req.URL.Path, "/v1/job/")
    46  	switch {
    47  	case strings.HasSuffix(path, "/evaluate"):
    48  		jobName := strings.TrimSuffix(path, "/evaluate")
    49  		return s.jobForceEvaluate(resp, req, jobName)
    50  	case strings.HasSuffix(path, "/allocations"):
    51  		jobName := strings.TrimSuffix(path, "/allocations")
    52  		return s.jobAllocations(resp, req, jobName)
    53  	case strings.HasSuffix(path, "/evaluations"):
    54  		jobName := strings.TrimSuffix(path, "/evaluations")
    55  		return s.jobEvaluations(resp, req, jobName)
    56  	case strings.HasSuffix(path, "/periodic/force"):
    57  		jobName := strings.TrimSuffix(path, "/periodic/force")
    58  		return s.periodicForceRequest(resp, req, jobName)
    59  	case strings.HasSuffix(path, "/plan"):
    60  		jobName := strings.TrimSuffix(path, "/plan")
    61  		return s.jobPlan(resp, req, jobName)
    62  	case strings.HasSuffix(path, "/summary"):
    63  		jobName := strings.TrimSuffix(path, "/summary")
    64  		return s.jobSummaryRequest(resp, req, jobName)
    65  	case strings.HasSuffix(path, "/dispatch"):
    66  		jobName := strings.TrimSuffix(path, "/dispatch")
    67  		return s.jobDispatchRequest(resp, req, jobName)
    68  	case strings.HasSuffix(path, "/versions"):
    69  		jobName := strings.TrimSuffix(path, "/versions")
    70  		return s.jobVersions(resp, req, jobName)
    71  	case strings.HasSuffix(path, "/revert"):
    72  		jobName := strings.TrimSuffix(path, "/revert")
    73  		return s.jobRevert(resp, req, jobName)
    74  	case strings.HasSuffix(path, "/deployments"):
    75  		jobName := strings.TrimSuffix(path, "/deployments")
    76  		return s.jobDeployments(resp, req, jobName)
    77  	case strings.HasSuffix(path, "/deployment"):
    78  		jobName := strings.TrimSuffix(path, "/deployment")
    79  		return s.jobLatestDeployment(resp, req, jobName)
    80  	case strings.HasSuffix(path, "/stable"):
    81  		jobName := strings.TrimSuffix(path, "/stable")
    82  		return s.jobStable(resp, req, jobName)
    83  	default:
    84  		return s.jobCRUD(resp, req, path)
    85  	}
    86  }
    87  
    88  func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Request,
    89  	jobName string) (interface{}, error) {
    90  	if req.Method != "PUT" && req.Method != "POST" {
    91  		return nil, CodedError(405, ErrInvalidMethod)
    92  	}
    93  	var args structs.JobEvaluateRequest
    94  
    95  	// TODO(preetha): remove in 0.9
    96  	// COMPAT: For backwards compatibility allow using this endpoint without a payload
    97  	if req.ContentLength == 0 {
    98  		args = structs.JobEvaluateRequest{
    99  			JobID: jobName,
   100  		}
   101  	} else {
   102  		if err := decodeBody(req, &args); err != nil {
   103  			return nil, CodedError(400, err.Error())
   104  		}
   105  		if args.JobID == "" {
   106  			return nil, CodedError(400, "Job ID must be specified")
   107  		}
   108  
   109  		if jobName != "" && args.JobID != jobName {
   110  			return nil, CodedError(400, "JobID not same as job name")
   111  		}
   112  	}
   113  	s.parseWriteRequest(req, &args.WriteRequest)
   114  
   115  	var out structs.JobRegisterResponse
   116  	if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil {
   117  		return nil, err
   118  	}
   119  	setIndex(resp, out.Index)
   120  	return out, nil
   121  }
   122  
   123  func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request,
   124  	jobName string) (interface{}, error) {
   125  	if req.Method != "PUT" && req.Method != "POST" {
   126  		return nil, CodedError(405, ErrInvalidMethod)
   127  	}
   128  
   129  	var args api.JobPlanRequest
   130  	if err := decodeBody(req, &args); err != nil {
   131  		return nil, CodedError(400, err.Error())
   132  	}
   133  	if args.Job == nil {
   134  		return nil, CodedError(400, "Job must be specified")
   135  	}
   136  	if args.Job.ID == nil {
   137  		return nil, CodedError(400, "Job must have a valid ID")
   138  	}
   139  	if jobName != "" && *args.Job.ID != jobName {
   140  		return nil, CodedError(400, "Job ID does not match")
   141  	}
   142  
   143  	sJob := ApiJobToStructJob(args.Job)
   144  	planReq := structs.JobPlanRequest{
   145  		Job:            sJob,
   146  		Diff:           args.Diff,
   147  		PolicyOverride: args.PolicyOverride,
   148  		WriteRequest: structs.WriteRequest{
   149  			Region: args.WriteRequest.Region,
   150  		},
   151  	}
   152  	s.parseWriteRequest(req, &planReq.WriteRequest)
   153  	planReq.Namespace = sJob.Namespace
   154  
   155  	var out structs.JobPlanResponse
   156  	if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil {
   157  		return nil, err
   158  	}
   159  	setIndex(resp, out.Index)
   160  	return out, nil
   161  }
   162  
   163  func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   164  	// Ensure request method is POST or PUT
   165  	if !(req.Method == "POST" || req.Method == "PUT") {
   166  		return nil, CodedError(405, ErrInvalidMethod)
   167  	}
   168  
   169  	var validateRequest api.JobValidateRequest
   170  	if err := decodeBody(req, &validateRequest); err != nil {
   171  		return nil, CodedError(400, err.Error())
   172  	}
   173  	if validateRequest.Job == nil {
   174  		return nil, CodedError(400, "Job must be specified")
   175  	}
   176  
   177  	job := ApiJobToStructJob(validateRequest.Job)
   178  	args := structs.JobValidateRequest{
   179  		Job: job,
   180  		WriteRequest: structs.WriteRequest{
   181  			Region: validateRequest.Region,
   182  		},
   183  	}
   184  	s.parseWriteRequest(req, &args.WriteRequest)
   185  	args.Namespace = job.Namespace
   186  
   187  	var out structs.JobValidateResponse
   188  	if err := s.agent.RPC("Job.Validate", &args, &out); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	return out, nil
   193  }
   194  
   195  func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request,
   196  	jobName string) (interface{}, error) {
   197  	if req.Method != "PUT" && req.Method != "POST" {
   198  		return nil, CodedError(405, ErrInvalidMethod)
   199  	}
   200  
   201  	args := structs.PeriodicForceRequest{
   202  		JobID: jobName,
   203  	}
   204  	s.parseWriteRequest(req, &args.WriteRequest)
   205  
   206  	var out structs.PeriodicForceResponse
   207  	if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil {
   208  		return nil, err
   209  	}
   210  	setIndex(resp, out.Index)
   211  	return out, nil
   212  }
   213  
   214  func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request,
   215  	jobName string) (interface{}, error) {
   216  	if req.Method != "GET" {
   217  		return nil, CodedError(405, ErrInvalidMethod)
   218  	}
   219  	allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all"))
   220  
   221  	args := structs.JobSpecificRequest{
   222  		JobID:     jobName,
   223  		AllAllocs: allAllocs,
   224  	}
   225  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   226  		return nil, nil
   227  	}
   228  
   229  	var out structs.JobAllocationsResponse
   230  	if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	setMeta(resp, &out.QueryMeta)
   235  	if out.Allocations == nil {
   236  		out.Allocations = make([]*structs.AllocListStub, 0)
   237  	}
   238  	for _, alloc := range out.Allocations {
   239  		alloc.SetEventDisplayMessages()
   240  	}
   241  	return out.Allocations, nil
   242  }
   243  
   244  func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request,
   245  	jobName string) (interface{}, error) {
   246  	if req.Method != "GET" {
   247  		return nil, CodedError(405, ErrInvalidMethod)
   248  	}
   249  	args := structs.JobSpecificRequest{
   250  		JobID: jobName,
   251  	}
   252  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   253  		return nil, nil
   254  	}
   255  
   256  	var out structs.JobEvaluationsResponse
   257  	if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	setMeta(resp, &out.QueryMeta)
   262  	if out.Evaluations == nil {
   263  		out.Evaluations = make([]*structs.Evaluation, 0)
   264  	}
   265  	return out.Evaluations, nil
   266  }
   267  
   268  func (s *HTTPServer) jobDeployments(resp http.ResponseWriter, req *http.Request,
   269  	jobName string) (interface{}, error) {
   270  	if req.Method != "GET" {
   271  		return nil, CodedError(405, ErrInvalidMethod)
   272  	}
   273  	args := structs.JobSpecificRequest{
   274  		JobID: jobName,
   275  	}
   276  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   277  		return nil, nil
   278  	}
   279  
   280  	var out structs.DeploymentListResponse
   281  	if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	setMeta(resp, &out.QueryMeta)
   286  	if out.Deployments == nil {
   287  		out.Deployments = make([]*structs.Deployment, 0)
   288  	}
   289  	return out.Deployments, nil
   290  }
   291  
   292  func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request,
   293  	jobName string) (interface{}, error) {
   294  	if req.Method != "GET" {
   295  		return nil, CodedError(405, ErrInvalidMethod)
   296  	}
   297  	args := structs.JobSpecificRequest{
   298  		JobID: jobName,
   299  	}
   300  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   301  		return nil, nil
   302  	}
   303  
   304  	var out structs.SingleDeploymentResponse
   305  	if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil {
   306  		return nil, err
   307  	}
   308  
   309  	setMeta(resp, &out.QueryMeta)
   310  	return out.Deployment, nil
   311  }
   312  
   313  func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request,
   314  	jobName string) (interface{}, error) {
   315  	switch req.Method {
   316  	case "GET":
   317  		return s.jobQuery(resp, req, jobName)
   318  	case "PUT", "POST":
   319  		return s.jobUpdate(resp, req, jobName)
   320  	case "DELETE":
   321  		return s.jobDelete(resp, req, jobName)
   322  	default:
   323  		return nil, CodedError(405, ErrInvalidMethod)
   324  	}
   325  }
   326  
   327  func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request,
   328  	jobName string) (interface{}, error) {
   329  	args := structs.JobSpecificRequest{
   330  		JobID: jobName,
   331  	}
   332  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   333  		return nil, nil
   334  	}
   335  
   336  	var out structs.SingleJobResponse
   337  	if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil {
   338  		return nil, err
   339  	}
   340  
   341  	setMeta(resp, &out.QueryMeta)
   342  	if out.Job == nil {
   343  		return nil, CodedError(404, "job not found")
   344  	}
   345  
   346  	// Decode the payload if there is any
   347  	job := out.Job
   348  	if len(job.Payload) != 0 {
   349  		decoded, err := snappy.Decode(nil, out.Job.Payload)
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  		job = job.Copy()
   354  		job.Payload = decoded
   355  	}
   356  
   357  	return job, nil
   358  }
   359  
   360  func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request,
   361  	jobName string) (interface{}, error) {
   362  	var args api.JobRegisterRequest
   363  	if err := decodeBody(req, &args); err != nil {
   364  		return nil, CodedError(400, err.Error())
   365  	}
   366  	if args.Job == nil {
   367  		return nil, CodedError(400, "Job must be specified")
   368  	}
   369  
   370  	if args.Job.ID == nil {
   371  		return nil, CodedError(400, "Job ID hasn't been provided")
   372  	}
   373  	if jobName != "" && *args.Job.ID != jobName {
   374  		return nil, CodedError(400, "Job ID does not match name")
   375  	}
   376  
   377  	sJob := ApiJobToStructJob(args.Job)
   378  
   379  	regReq := structs.JobRegisterRequest{
   380  		Job:            sJob,
   381  		EnforceIndex:   args.EnforceIndex,
   382  		JobModifyIndex: args.JobModifyIndex,
   383  		PolicyOverride: args.PolicyOverride,
   384  		WriteRequest: structs.WriteRequest{
   385  			Region:    args.WriteRequest.Region,
   386  			AuthToken: args.WriteRequest.SecretID,
   387  		},
   388  	}
   389  	s.parseWriteRequest(req, &regReq.WriteRequest)
   390  	regReq.Namespace = sJob.Namespace
   391  
   392  	var out structs.JobRegisterResponse
   393  	if err := s.agent.RPC("Job.Register", &regReq, &out); err != nil {
   394  		return nil, err
   395  	}
   396  	setIndex(resp, out.Index)
   397  	return out, nil
   398  }
   399  
   400  func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request,
   401  	jobName string) (interface{}, error) {
   402  
   403  	purgeStr := req.URL.Query().Get("purge")
   404  	var purgeBool bool
   405  	if purgeStr != "" {
   406  		var err error
   407  		purgeBool, err = strconv.ParseBool(purgeStr)
   408  		if err != nil {
   409  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err)
   410  		}
   411  	}
   412  
   413  	args := structs.JobDeregisterRequest{
   414  		JobID: jobName,
   415  		Purge: purgeBool,
   416  	}
   417  	s.parseWriteRequest(req, &args.WriteRequest)
   418  
   419  	var out structs.JobDeregisterResponse
   420  	if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil {
   421  		return nil, err
   422  	}
   423  	setIndex(resp, out.Index)
   424  	return out, nil
   425  }
   426  
   427  func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request,
   428  	jobName string) (interface{}, error) {
   429  
   430  	diffsStr := req.URL.Query().Get("diffs")
   431  	var diffsBool bool
   432  	if diffsStr != "" {
   433  		var err error
   434  		diffsBool, err = strconv.ParseBool(diffsStr)
   435  		if err != nil {
   436  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err)
   437  		}
   438  	}
   439  
   440  	args := structs.JobVersionsRequest{
   441  		JobID: jobName,
   442  		Diffs: diffsBool,
   443  	}
   444  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   445  		return nil, nil
   446  	}
   447  
   448  	var out structs.JobVersionsResponse
   449  	if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil {
   450  		return nil, err
   451  	}
   452  
   453  	setMeta(resp, &out.QueryMeta)
   454  	if len(out.Versions) == 0 {
   455  		return nil, CodedError(404, "job versions not found")
   456  	}
   457  
   458  	return out, nil
   459  }
   460  
   461  func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request,
   462  	jobName string) (interface{}, error) {
   463  
   464  	if req.Method != "PUT" && req.Method != "POST" {
   465  		return nil, CodedError(405, ErrInvalidMethod)
   466  	}
   467  
   468  	var revertRequest structs.JobRevertRequest
   469  	if err := decodeBody(req, &revertRequest); err != nil {
   470  		return nil, CodedError(400, err.Error())
   471  	}
   472  	if revertRequest.JobID == "" {
   473  		return nil, CodedError(400, "JobID must be specified")
   474  	}
   475  	if revertRequest.JobID != jobName {
   476  		return nil, CodedError(400, "Job ID does not match")
   477  	}
   478  
   479  	s.parseWriteRequest(req, &revertRequest.WriteRequest)
   480  
   481  	var out structs.JobRegisterResponse
   482  	if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil {
   483  		return nil, err
   484  	}
   485  
   486  	setMeta(resp, &out.QueryMeta)
   487  	return out, nil
   488  }
   489  
   490  func (s *HTTPServer) jobStable(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 stableRequest structs.JobStabilityRequest
   498  	if err := decodeBody(req, &stableRequest); err != nil {
   499  		return nil, CodedError(400, err.Error())
   500  	}
   501  	if stableRequest.JobID == "" {
   502  		return nil, CodedError(400, "JobID must be specified")
   503  	}
   504  	if stableRequest.JobID != jobName {
   505  		return nil, CodedError(400, "Job ID does not match")
   506  	}
   507  
   508  	s.parseWriteRequest(req, &stableRequest.WriteRequest)
   509  
   510  	var out structs.JobStabilityResponse
   511  	if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil {
   512  		return nil, err
   513  	}
   514  
   515  	setIndex(resp, out.Index)
   516  	return out, nil
   517  }
   518  
   519  func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   520  	args := structs.JobSummaryRequest{
   521  		JobID: name,
   522  	}
   523  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   524  		return nil, nil
   525  	}
   526  
   527  	var out structs.JobSummaryResponse
   528  	if err := s.agent.RPC("Job.Summary", &args, &out); err != nil {
   529  		return nil, err
   530  	}
   531  
   532  	setMeta(resp, &out.QueryMeta)
   533  	if out.JobSummary == nil {
   534  		return nil, CodedError(404, "job not found")
   535  	}
   536  	setIndex(resp, out.Index)
   537  	return out.JobSummary, nil
   538  }
   539  
   540  func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   541  	if req.Method != "PUT" && req.Method != "POST" {
   542  		return nil, CodedError(405, ErrInvalidMethod)
   543  	}
   544  	args := structs.JobDispatchRequest{}
   545  	if err := decodeBody(req, &args); err != nil {
   546  		return nil, CodedError(400, err.Error())
   547  	}
   548  	if args.JobID != "" && args.JobID != name {
   549  		return nil, CodedError(400, "Job ID does not match")
   550  	}
   551  	if args.JobID == "" {
   552  		args.JobID = name
   553  	}
   554  
   555  	s.parseWriteRequest(req, &args.WriteRequest)
   556  
   557  	var out structs.JobDispatchResponse
   558  	if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil {
   559  		return nil, err
   560  	}
   561  	setIndex(resp, out.Index)
   562  	return out, nil
   563  }
   564  
   565  // JobsParseRequest parses a hcl jobspec and returns a api.Job
   566  func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   567  	if req.Method != http.MethodPut && req.Method != http.MethodPost {
   568  		return nil, CodedError(405, ErrInvalidMethod)
   569  	}
   570  
   571  	args := &api.JobsParseRequest{}
   572  	if err := decodeBody(req, &args); err != nil {
   573  		return nil, CodedError(400, err.Error())
   574  	}
   575  	if args.JobHCL == "" {
   576  		return nil, CodedError(400, "Job spec is empty")
   577  	}
   578  
   579  	jobfile := strings.NewReader(args.JobHCL)
   580  	jobStruct, err := jobspec.Parse(jobfile)
   581  	if err != nil {
   582  		return nil, CodedError(400, err.Error())
   583  	}
   584  
   585  	if args.Canonicalize {
   586  		jobStruct.Canonicalize()
   587  	}
   588  	return jobStruct, nil
   589  }
   590  
   591  func ApiJobToStructJob(job *api.Job) *structs.Job {
   592  	job.Canonicalize()
   593  
   594  	j := &structs.Job{
   595  		Stop:        *job.Stop,
   596  		Region:      *job.Region,
   597  		Namespace:   *job.Namespace,
   598  		ID:          *job.ID,
   599  		ParentID:    *job.ParentID,
   600  		Name:        *job.Name,
   601  		Type:        *job.Type,
   602  		Priority:    *job.Priority,
   603  		AllAtOnce:   *job.AllAtOnce,
   604  		Datacenters: job.Datacenters,
   605  		Payload:     job.Payload,
   606  		Meta:        job.Meta,
   607  		VaultToken:  *job.VaultToken,
   608  	}
   609  
   610  	if l := len(job.Constraints); l != 0 {
   611  		j.Constraints = make([]*structs.Constraint, l)
   612  		for i, c := range job.Constraints {
   613  			con := &structs.Constraint{}
   614  			ApiConstraintToStructs(c, con)
   615  			j.Constraints[i] = con
   616  		}
   617  	}
   618  
   619  	if l := len(job.Affinities); l != 0 {
   620  		j.Affinities = make([]*structs.Affinity, l)
   621  		for i, a := range job.Affinities {
   622  			j.Affinities[i] = ApiAffinityToStructs(a)
   623  		}
   624  	}
   625  
   626  	// COMPAT: Remove in 0.7.0. Update has been pushed into the task groups
   627  	if job.Update != nil {
   628  		j.Update = structs.UpdateStrategy{}
   629  
   630  		if job.Update.Stagger != nil {
   631  			j.Update.Stagger = *job.Update.Stagger
   632  		}
   633  		if job.Update.MaxParallel != nil {
   634  			j.Update.MaxParallel = *job.Update.MaxParallel
   635  		}
   636  	}
   637  
   638  	if l := len(job.Spreads); l != 0 {
   639  		j.Spreads = make([]*structs.Spread, l)
   640  		for i, apiSpread := range job.Spreads {
   641  			j.Spreads[i] = ApiSpreadToStructs(apiSpread)
   642  		}
   643  	}
   644  
   645  	if job.Periodic != nil {
   646  		j.Periodic = &structs.PeriodicConfig{
   647  			Enabled:         *job.Periodic.Enabled,
   648  			SpecType:        *job.Periodic.SpecType,
   649  			ProhibitOverlap: *job.Periodic.ProhibitOverlap,
   650  			TimeZone:        *job.Periodic.TimeZone,
   651  		}
   652  
   653  		if job.Periodic.Spec != nil {
   654  			j.Periodic.Spec = *job.Periodic.Spec
   655  		}
   656  	}
   657  
   658  	if job.ParameterizedJob != nil {
   659  		j.ParameterizedJob = &structs.ParameterizedJobConfig{
   660  			Payload:      job.ParameterizedJob.Payload,
   661  			MetaRequired: job.ParameterizedJob.MetaRequired,
   662  			MetaOptional: job.ParameterizedJob.MetaOptional,
   663  		}
   664  	}
   665  
   666  	if l := len(job.TaskGroups); l != 0 {
   667  		j.TaskGroups = make([]*structs.TaskGroup, l)
   668  		for i, taskGroup := range job.TaskGroups {
   669  			tg := &structs.TaskGroup{}
   670  			ApiTgToStructsTG(taskGroup, tg)
   671  			j.TaskGroups[i] = tg
   672  		}
   673  	}
   674  
   675  	return j
   676  }
   677  
   678  func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
   679  	tg.Name = *taskGroup.Name
   680  	tg.Count = *taskGroup.Count
   681  	tg.Meta = taskGroup.Meta
   682  
   683  	if l := len(taskGroup.Constraints); l != 0 {
   684  		tg.Constraints = make([]*structs.Constraint, l)
   685  		for k, constraint := range taskGroup.Constraints {
   686  			c := &structs.Constraint{}
   687  			ApiConstraintToStructs(constraint, c)
   688  			tg.Constraints[k] = c
   689  		}
   690  	}
   691  
   692  	if l := len(taskGroup.Affinities); l != 0 {
   693  		tg.Affinities = make([]*structs.Affinity, l)
   694  		for k, affinity := range taskGroup.Affinities {
   695  			tg.Affinities[k] = ApiAffinityToStructs(affinity)
   696  		}
   697  	}
   698  
   699  	tg.RestartPolicy = &structs.RestartPolicy{
   700  		Attempts: *taskGroup.RestartPolicy.Attempts,
   701  		Interval: *taskGroup.RestartPolicy.Interval,
   702  		Delay:    *taskGroup.RestartPolicy.Delay,
   703  		Mode:     *taskGroup.RestartPolicy.Mode,
   704  	}
   705  
   706  	if taskGroup.ReschedulePolicy != nil {
   707  		tg.ReschedulePolicy = &structs.ReschedulePolicy{
   708  			Attempts:      *taskGroup.ReschedulePolicy.Attempts,
   709  			Interval:      *taskGroup.ReschedulePolicy.Interval,
   710  			Delay:         *taskGroup.ReschedulePolicy.Delay,
   711  			DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction,
   712  			MaxDelay:      *taskGroup.ReschedulePolicy.MaxDelay,
   713  			Unlimited:     *taskGroup.ReschedulePolicy.Unlimited,
   714  		}
   715  	}
   716  
   717  	if taskGroup.Migrate != nil {
   718  		tg.Migrate = &structs.MigrateStrategy{
   719  			MaxParallel:     *taskGroup.Migrate.MaxParallel,
   720  			HealthCheck:     *taskGroup.Migrate.HealthCheck,
   721  			MinHealthyTime:  *taskGroup.Migrate.MinHealthyTime,
   722  			HealthyDeadline: *taskGroup.Migrate.HealthyDeadline,
   723  		}
   724  	}
   725  
   726  	tg.EphemeralDisk = &structs.EphemeralDisk{
   727  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   728  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   729  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   730  	}
   731  
   732  	if l := len(taskGroup.Spreads); l != 0 {
   733  		tg.Spreads = make([]*structs.Spread, l)
   734  		for k, spread := range taskGroup.Spreads {
   735  			tg.Spreads[k] = ApiSpreadToStructs(spread)
   736  		}
   737  	}
   738  
   739  	if taskGroup.Update != nil {
   740  		tg.Update = &structs.UpdateStrategy{
   741  			Stagger:          *taskGroup.Update.Stagger,
   742  			MaxParallel:      *taskGroup.Update.MaxParallel,
   743  			HealthCheck:      *taskGroup.Update.HealthCheck,
   744  			MinHealthyTime:   *taskGroup.Update.MinHealthyTime,
   745  			HealthyDeadline:  *taskGroup.Update.HealthyDeadline,
   746  			ProgressDeadline: *taskGroup.Update.ProgressDeadline,
   747  			AutoRevert:       *taskGroup.Update.AutoRevert,
   748  			Canary:           *taskGroup.Update.Canary,
   749  		}
   750  	}
   751  
   752  	if l := len(taskGroup.Tasks); l != 0 {
   753  		tg.Tasks = make([]*structs.Task, l)
   754  		for l, task := range taskGroup.Tasks {
   755  			t := &structs.Task{}
   756  			ApiTaskToStructsTask(task, t)
   757  			tg.Tasks[l] = t
   758  		}
   759  	}
   760  }
   761  
   762  // ApiTaskToStructsTask is a copy and type conversion between the API
   763  // representation of a task from a struct representation of a task.
   764  func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
   765  	structsTask.Name = apiTask.Name
   766  	structsTask.Driver = apiTask.Driver
   767  	structsTask.User = apiTask.User
   768  	structsTask.Leader = apiTask.Leader
   769  	structsTask.Config = apiTask.Config
   770  	structsTask.Env = apiTask.Env
   771  	structsTask.Meta = apiTask.Meta
   772  	structsTask.KillTimeout = *apiTask.KillTimeout
   773  	structsTask.ShutdownDelay = apiTask.ShutdownDelay
   774  	structsTask.KillSignal = apiTask.KillSignal
   775  
   776  	if l := len(apiTask.Constraints); l != 0 {
   777  		structsTask.Constraints = make([]*structs.Constraint, l)
   778  		for i, constraint := range apiTask.Constraints {
   779  			c := &structs.Constraint{}
   780  			ApiConstraintToStructs(constraint, c)
   781  			structsTask.Constraints[i] = c
   782  		}
   783  	}
   784  
   785  	if l := len(apiTask.Affinities); l != 0 {
   786  		structsTask.Affinities = make([]*structs.Affinity, l)
   787  		for i, a := range apiTask.Affinities {
   788  			structsTask.Affinities[i] = ApiAffinityToStructs(a)
   789  		}
   790  	}
   791  
   792  	if l := len(apiTask.Services); l != 0 {
   793  		structsTask.Services = make([]*structs.Service, l)
   794  		for i, service := range apiTask.Services {
   795  			structsTask.Services[i] = &structs.Service{
   796  				Name:        service.Name,
   797  				PortLabel:   service.PortLabel,
   798  				Tags:        service.Tags,
   799  				CanaryTags:  service.CanaryTags,
   800  				AddressMode: service.AddressMode,
   801  			}
   802  
   803  			if l := len(service.Checks); l != 0 {
   804  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   805  				for j, check := range service.Checks {
   806  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   807  						Name:          check.Name,
   808  						Type:          check.Type,
   809  						Command:       check.Command,
   810  						Args:          check.Args,
   811  						Path:          check.Path,
   812  						Protocol:      check.Protocol,
   813  						PortLabel:     check.PortLabel,
   814  						AddressMode:   check.AddressMode,
   815  						Interval:      check.Interval,
   816  						Timeout:       check.Timeout,
   817  						InitialStatus: check.InitialStatus,
   818  						TLSSkipVerify: check.TLSSkipVerify,
   819  						Header:        check.Header,
   820  						Method:        check.Method,
   821  						GRPCService:   check.GRPCService,
   822  						GRPCUseTLS:    check.GRPCUseTLS,
   823  					}
   824  					if check.CheckRestart != nil {
   825  						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
   826  							Limit:          check.CheckRestart.Limit,
   827  							Grace:          *check.CheckRestart.Grace,
   828  							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
   829  						}
   830  					}
   831  				}
   832  			}
   833  		}
   834  	}
   835  
   836  	structsTask.Resources = &structs.Resources{
   837  		CPU:      *apiTask.Resources.CPU,
   838  		MemoryMB: *apiTask.Resources.MemoryMB,
   839  		IOPS:     *apiTask.Resources.IOPS,
   840  	}
   841  
   842  	if l := len(apiTask.Resources.Networks); l != 0 {
   843  		structsTask.Resources.Networks = make([]*structs.NetworkResource, l)
   844  		for i, nw := range apiTask.Resources.Networks {
   845  			structsTask.Resources.Networks[i] = &structs.NetworkResource{
   846  				CIDR:  nw.CIDR,
   847  				IP:    nw.IP,
   848  				MBits: *nw.MBits,
   849  			}
   850  
   851  			if l := len(nw.DynamicPorts); l != 0 {
   852  				structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, l)
   853  				for j, dp := range nw.DynamicPorts {
   854  					structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{
   855  						Label: dp.Label,
   856  						Value: dp.Value,
   857  					}
   858  				}
   859  			}
   860  
   861  			if l := len(nw.ReservedPorts); l != 0 {
   862  				structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, l)
   863  				for j, rp := range nw.ReservedPorts {
   864  					structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{
   865  						Label: rp.Label,
   866  						Value: rp.Value,
   867  					}
   868  				}
   869  			}
   870  		}
   871  	}
   872  
   873  	structsTask.LogConfig = &structs.LogConfig{
   874  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   875  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   876  	}
   877  
   878  	if l := len(apiTask.Artifacts); l != 0 {
   879  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   880  		for k, ta := range apiTask.Artifacts {
   881  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   882  				GetterSource:  *ta.GetterSource,
   883  				GetterOptions: ta.GetterOptions,
   884  				GetterMode:    *ta.GetterMode,
   885  				RelativeDest:  *ta.RelativeDest,
   886  			}
   887  		}
   888  	}
   889  
   890  	if apiTask.Vault != nil {
   891  		structsTask.Vault = &structs.Vault{
   892  			Policies:     apiTask.Vault.Policies,
   893  			Env:          *apiTask.Vault.Env,
   894  			ChangeMode:   *apiTask.Vault.ChangeMode,
   895  			ChangeSignal: *apiTask.Vault.ChangeSignal,
   896  		}
   897  	}
   898  
   899  	if l := len(apiTask.Templates); l != 0 {
   900  		structsTask.Templates = make([]*structs.Template, l)
   901  		for i, template := range apiTask.Templates {
   902  			structsTask.Templates[i] = &structs.Template{
   903  				SourcePath:   *template.SourcePath,
   904  				DestPath:     *template.DestPath,
   905  				EmbeddedTmpl: *template.EmbeddedTmpl,
   906  				ChangeMode:   *template.ChangeMode,
   907  				ChangeSignal: *template.ChangeSignal,
   908  				Splay:        *template.Splay,
   909  				Perms:        *template.Perms,
   910  				LeftDelim:    *template.LeftDelim,
   911  				RightDelim:   *template.RightDelim,
   912  				Envvars:      *template.Envvars,
   913  				VaultGrace:   *template.VaultGrace,
   914  			}
   915  		}
   916  	}
   917  
   918  	if apiTask.DispatchPayload != nil {
   919  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
   920  			File: apiTask.DispatchPayload.File,
   921  		}
   922  	}
   923  }
   924  
   925  func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) {
   926  	c2.LTarget = c1.LTarget
   927  	c2.RTarget = c1.RTarget
   928  	c2.Operand = c1.Operand
   929  }
   930  
   931  func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity {
   932  	return &structs.Affinity{
   933  		LTarget: a1.LTarget,
   934  		Operand: a1.Operand,
   935  		RTarget: a1.RTarget,
   936  		Weight:  a1.Weight,
   937  	}
   938  }
   939  
   940  func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread {
   941  	ret := &structs.Spread{}
   942  	ret.Attribute = a1.Attribute
   943  	ret.Weight = a1.Weight
   944  	if a1.SpreadTarget != nil {
   945  		ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget))
   946  		for i, st := range a1.SpreadTarget {
   947  			ret.SpreadTarget[i] = &structs.SpreadTarget{
   948  				Value:   st.Value,
   949  				Percent: st.Percent,
   950  			}
   951  		}
   952  	}
   953  	return ret
   954  }