github.com/remilapeyre/nomad@v0.8.5/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  	// COMPAT: Remove in 0.7.0. Update has been pushed into the task groups
   620  	if job.Update != nil {
   621  		j.Update = structs.UpdateStrategy{}
   622  
   623  		if job.Update.Stagger != nil {
   624  			j.Update.Stagger = *job.Update.Stagger
   625  		}
   626  		if job.Update.MaxParallel != nil {
   627  			j.Update.MaxParallel = *job.Update.MaxParallel
   628  		}
   629  	}
   630  
   631  	if job.Periodic != nil {
   632  		j.Periodic = &structs.PeriodicConfig{
   633  			Enabled:         *job.Periodic.Enabled,
   634  			SpecType:        *job.Periodic.SpecType,
   635  			ProhibitOverlap: *job.Periodic.ProhibitOverlap,
   636  			TimeZone:        *job.Periodic.TimeZone,
   637  		}
   638  
   639  		if job.Periodic.Spec != nil {
   640  			j.Periodic.Spec = *job.Periodic.Spec
   641  		}
   642  	}
   643  
   644  	if job.ParameterizedJob != nil {
   645  		j.ParameterizedJob = &structs.ParameterizedJobConfig{
   646  			Payload:      job.ParameterizedJob.Payload,
   647  			MetaRequired: job.ParameterizedJob.MetaRequired,
   648  			MetaOptional: job.ParameterizedJob.MetaOptional,
   649  		}
   650  	}
   651  
   652  	if l := len(job.TaskGroups); l != 0 {
   653  		j.TaskGroups = make([]*structs.TaskGroup, l)
   654  		for i, taskGroup := range job.TaskGroups {
   655  			tg := &structs.TaskGroup{}
   656  			ApiTgToStructsTG(taskGroup, tg)
   657  			j.TaskGroups[i] = tg
   658  		}
   659  	}
   660  
   661  	return j
   662  }
   663  
   664  func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
   665  	tg.Name = *taskGroup.Name
   666  	tg.Count = *taskGroup.Count
   667  	tg.Meta = taskGroup.Meta
   668  
   669  	if l := len(taskGroup.Constraints); l != 0 {
   670  		tg.Constraints = make([]*structs.Constraint, l)
   671  		for k, constraint := range taskGroup.Constraints {
   672  			c := &structs.Constraint{}
   673  			ApiConstraintToStructs(constraint, c)
   674  			tg.Constraints[k] = c
   675  		}
   676  	}
   677  
   678  	tg.RestartPolicy = &structs.RestartPolicy{
   679  		Attempts: *taskGroup.RestartPolicy.Attempts,
   680  		Interval: *taskGroup.RestartPolicy.Interval,
   681  		Delay:    *taskGroup.RestartPolicy.Delay,
   682  		Mode:     *taskGroup.RestartPolicy.Mode,
   683  	}
   684  
   685  	if taskGroup.ReschedulePolicy != nil {
   686  		tg.ReschedulePolicy = &structs.ReschedulePolicy{
   687  			Attempts:      *taskGroup.ReschedulePolicy.Attempts,
   688  			Interval:      *taskGroup.ReschedulePolicy.Interval,
   689  			Delay:         *taskGroup.ReschedulePolicy.Delay,
   690  			DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction,
   691  			MaxDelay:      *taskGroup.ReschedulePolicy.MaxDelay,
   692  			Unlimited:     *taskGroup.ReschedulePolicy.Unlimited,
   693  		}
   694  	}
   695  
   696  	if taskGroup.Migrate != nil {
   697  		tg.Migrate = &structs.MigrateStrategy{
   698  			MaxParallel:     *taskGroup.Migrate.MaxParallel,
   699  			HealthCheck:     *taskGroup.Migrate.HealthCheck,
   700  			MinHealthyTime:  *taskGroup.Migrate.MinHealthyTime,
   701  			HealthyDeadline: *taskGroup.Migrate.HealthyDeadline,
   702  		}
   703  	}
   704  
   705  	tg.EphemeralDisk = &structs.EphemeralDisk{
   706  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   707  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   708  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   709  	}
   710  
   711  	if taskGroup.Update != nil {
   712  		tg.Update = &structs.UpdateStrategy{
   713  			Stagger:          *taskGroup.Update.Stagger,
   714  			MaxParallel:      *taskGroup.Update.MaxParallel,
   715  			HealthCheck:      *taskGroup.Update.HealthCheck,
   716  			MinHealthyTime:   *taskGroup.Update.MinHealthyTime,
   717  			HealthyDeadline:  *taskGroup.Update.HealthyDeadline,
   718  			ProgressDeadline: *taskGroup.Update.ProgressDeadline,
   719  			AutoRevert:       *taskGroup.Update.AutoRevert,
   720  			Canary:           *taskGroup.Update.Canary,
   721  		}
   722  	}
   723  
   724  	if l := len(taskGroup.Tasks); l != 0 {
   725  		tg.Tasks = make([]*structs.Task, l)
   726  		for l, task := range taskGroup.Tasks {
   727  			t := &structs.Task{}
   728  			ApiTaskToStructsTask(task, t)
   729  			tg.Tasks[l] = t
   730  		}
   731  	}
   732  }
   733  
   734  // ApiTaskToStructsTask is a copy and type conversion between the API
   735  // representation of a task from a struct representation of a task.
   736  func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
   737  	structsTask.Name = apiTask.Name
   738  	structsTask.Driver = apiTask.Driver
   739  	structsTask.User = apiTask.User
   740  	structsTask.Leader = apiTask.Leader
   741  	structsTask.Config = apiTask.Config
   742  	structsTask.Env = apiTask.Env
   743  	structsTask.Meta = apiTask.Meta
   744  	structsTask.KillTimeout = *apiTask.KillTimeout
   745  	structsTask.ShutdownDelay = apiTask.ShutdownDelay
   746  	structsTask.KillSignal = apiTask.KillSignal
   747  
   748  	if l := len(apiTask.Constraints); l != 0 {
   749  		structsTask.Constraints = make([]*structs.Constraint, l)
   750  		for i, constraint := range apiTask.Constraints {
   751  			c := &structs.Constraint{}
   752  			ApiConstraintToStructs(constraint, c)
   753  			structsTask.Constraints[i] = c
   754  		}
   755  	}
   756  
   757  	if l := len(apiTask.Services); l != 0 {
   758  		structsTask.Services = make([]*structs.Service, l)
   759  		for i, service := range apiTask.Services {
   760  			structsTask.Services[i] = &structs.Service{
   761  				Name:        service.Name,
   762  				PortLabel:   service.PortLabel,
   763  				Tags:        service.Tags,
   764  				CanaryTags:  service.CanaryTags,
   765  				AddressMode: service.AddressMode,
   766  			}
   767  
   768  			if l := len(service.Checks); l != 0 {
   769  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   770  				for j, check := range service.Checks {
   771  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   772  						Name:          check.Name,
   773  						Type:          check.Type,
   774  						Command:       check.Command,
   775  						Args:          check.Args,
   776  						Path:          check.Path,
   777  						Protocol:      check.Protocol,
   778  						PortLabel:     check.PortLabel,
   779  						AddressMode:   check.AddressMode,
   780  						Interval:      check.Interval,
   781  						Timeout:       check.Timeout,
   782  						InitialStatus: check.InitialStatus,
   783  						TLSSkipVerify: check.TLSSkipVerify,
   784  						Header:        check.Header,
   785  						Method:        check.Method,
   786  						GRPCService:   check.GRPCService,
   787  						GRPCUseTLS:    check.GRPCUseTLS,
   788  					}
   789  					if check.CheckRestart != nil {
   790  						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
   791  							Limit:          check.CheckRestart.Limit,
   792  							Grace:          *check.CheckRestart.Grace,
   793  							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
   794  						}
   795  					}
   796  				}
   797  			}
   798  		}
   799  	}
   800  
   801  	structsTask.Resources = &structs.Resources{
   802  		CPU:      *apiTask.Resources.CPU,
   803  		MemoryMB: *apiTask.Resources.MemoryMB,
   804  		IOPS:     *apiTask.Resources.IOPS,
   805  	}
   806  
   807  	if l := len(apiTask.Resources.Networks); l != 0 {
   808  		structsTask.Resources.Networks = make([]*structs.NetworkResource, l)
   809  		for i, nw := range apiTask.Resources.Networks {
   810  			structsTask.Resources.Networks[i] = &structs.NetworkResource{
   811  				CIDR:  nw.CIDR,
   812  				IP:    nw.IP,
   813  				MBits: *nw.MBits,
   814  			}
   815  
   816  			if l := len(nw.DynamicPorts); l != 0 {
   817  				structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, l)
   818  				for j, dp := range nw.DynamicPorts {
   819  					structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{
   820  						Label: dp.Label,
   821  						Value: dp.Value,
   822  					}
   823  				}
   824  			}
   825  
   826  			if l := len(nw.ReservedPorts); l != 0 {
   827  				structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, l)
   828  				for j, rp := range nw.ReservedPorts {
   829  					structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{
   830  						Label: rp.Label,
   831  						Value: rp.Value,
   832  					}
   833  				}
   834  			}
   835  		}
   836  	}
   837  
   838  	structsTask.LogConfig = &structs.LogConfig{
   839  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   840  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   841  	}
   842  
   843  	if l := len(apiTask.Artifacts); l != 0 {
   844  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   845  		for k, ta := range apiTask.Artifacts {
   846  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   847  				GetterSource:  *ta.GetterSource,
   848  				GetterOptions: ta.GetterOptions,
   849  				GetterMode:    *ta.GetterMode,
   850  				RelativeDest:  *ta.RelativeDest,
   851  			}
   852  		}
   853  	}
   854  
   855  	if apiTask.Vault != nil {
   856  		structsTask.Vault = &structs.Vault{
   857  			Policies:     apiTask.Vault.Policies,
   858  			Env:          *apiTask.Vault.Env,
   859  			ChangeMode:   *apiTask.Vault.ChangeMode,
   860  			ChangeSignal: *apiTask.Vault.ChangeSignal,
   861  		}
   862  	}
   863  
   864  	if l := len(apiTask.Templates); l != 0 {
   865  		structsTask.Templates = make([]*structs.Template, l)
   866  		for i, template := range apiTask.Templates {
   867  			structsTask.Templates[i] = &structs.Template{
   868  				SourcePath:   *template.SourcePath,
   869  				DestPath:     *template.DestPath,
   870  				EmbeddedTmpl: *template.EmbeddedTmpl,
   871  				ChangeMode:   *template.ChangeMode,
   872  				ChangeSignal: *template.ChangeSignal,
   873  				Splay:        *template.Splay,
   874  				Perms:        *template.Perms,
   875  				LeftDelim:    *template.LeftDelim,
   876  				RightDelim:   *template.RightDelim,
   877  				Envvars:      *template.Envvars,
   878  				VaultGrace:   *template.VaultGrace,
   879  			}
   880  		}
   881  	}
   882  
   883  	if apiTask.DispatchPayload != nil {
   884  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
   885  			File: apiTask.DispatchPayload.File,
   886  		}
   887  	}
   888  }
   889  
   890  func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) {
   891  	c2.LTarget = c1.LTarget
   892  	c2.RTarget = c1.RTarget
   893  	c2.Operand = c1.Operand
   894  }