github.com/smithx10/nomad@v0.9.1-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/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  		Constraints: ApiConstraintsToStructs(job.Constraints),
   609  		Affinities:  ApiAffinitiesToStructs(job.Affinities),
   610  	}
   611  
   612  	// COMPAT: Remove in 0.7.0. Update has been pushed into the task groups
   613  	if job.Update != nil {
   614  		j.Update = structs.UpdateStrategy{}
   615  
   616  		if job.Update.Stagger != nil {
   617  			j.Update.Stagger = *job.Update.Stagger
   618  		}
   619  		if job.Update.MaxParallel != nil {
   620  			j.Update.MaxParallel = *job.Update.MaxParallel
   621  		}
   622  	}
   623  
   624  	if l := len(job.Spreads); l != 0 {
   625  		j.Spreads = make([]*structs.Spread, l)
   626  		for i, apiSpread := range job.Spreads {
   627  			j.Spreads[i] = ApiSpreadToStructs(apiSpread)
   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  	tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints)
   669  	tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities)
   670  
   671  	tg.RestartPolicy = &structs.RestartPolicy{
   672  		Attempts: *taskGroup.RestartPolicy.Attempts,
   673  		Interval: *taskGroup.RestartPolicy.Interval,
   674  		Delay:    *taskGroup.RestartPolicy.Delay,
   675  		Mode:     *taskGroup.RestartPolicy.Mode,
   676  	}
   677  
   678  	if taskGroup.ReschedulePolicy != nil {
   679  		tg.ReschedulePolicy = &structs.ReschedulePolicy{
   680  			Attempts:      *taskGroup.ReschedulePolicy.Attempts,
   681  			Interval:      *taskGroup.ReschedulePolicy.Interval,
   682  			Delay:         *taskGroup.ReschedulePolicy.Delay,
   683  			DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction,
   684  			MaxDelay:      *taskGroup.ReschedulePolicy.MaxDelay,
   685  			Unlimited:     *taskGroup.ReschedulePolicy.Unlimited,
   686  		}
   687  	}
   688  
   689  	if taskGroup.Migrate != nil {
   690  		tg.Migrate = &structs.MigrateStrategy{
   691  			MaxParallel:     *taskGroup.Migrate.MaxParallel,
   692  			HealthCheck:     *taskGroup.Migrate.HealthCheck,
   693  			MinHealthyTime:  *taskGroup.Migrate.MinHealthyTime,
   694  			HealthyDeadline: *taskGroup.Migrate.HealthyDeadline,
   695  		}
   696  	}
   697  
   698  	tg.EphemeralDisk = &structs.EphemeralDisk{
   699  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   700  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   701  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   702  	}
   703  
   704  	if l := len(taskGroup.Spreads); l != 0 {
   705  		tg.Spreads = make([]*structs.Spread, l)
   706  		for k, spread := range taskGroup.Spreads {
   707  			tg.Spreads[k] = ApiSpreadToStructs(spread)
   708  		}
   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  	structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints)
   748  	structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities)
   749  
   750  	if l := len(apiTask.Services); l != 0 {
   751  		structsTask.Services = make([]*structs.Service, l)
   752  		for i, service := range apiTask.Services {
   753  			structsTask.Services[i] = &structs.Service{
   754  				Name:        service.Name,
   755  				PortLabel:   service.PortLabel,
   756  				Tags:        service.Tags,
   757  				CanaryTags:  service.CanaryTags,
   758  				AddressMode: service.AddressMode,
   759  			}
   760  
   761  			if l := len(service.Checks); l != 0 {
   762  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   763  				for j, check := range service.Checks {
   764  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   765  						Name:          check.Name,
   766  						Type:          check.Type,
   767  						Command:       check.Command,
   768  						Args:          check.Args,
   769  						Path:          check.Path,
   770  						Protocol:      check.Protocol,
   771  						PortLabel:     check.PortLabel,
   772  						AddressMode:   check.AddressMode,
   773  						Interval:      check.Interval,
   774  						Timeout:       check.Timeout,
   775  						InitialStatus: check.InitialStatus,
   776  						TLSSkipVerify: check.TLSSkipVerify,
   777  						Header:        check.Header,
   778  						Method:        check.Method,
   779  						GRPCService:   check.GRPCService,
   780  						GRPCUseTLS:    check.GRPCUseTLS,
   781  					}
   782  					if check.CheckRestart != nil {
   783  						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
   784  							Limit:          check.CheckRestart.Limit,
   785  							Grace:          *check.CheckRestart.Grace,
   786  							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
   787  						}
   788  					}
   789  				}
   790  			}
   791  		}
   792  	}
   793  
   794  	structsTask.Resources = ApiResourcesToStructs(apiTask.Resources)
   795  
   796  	structsTask.LogConfig = &structs.LogConfig{
   797  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   798  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   799  	}
   800  
   801  	if l := len(apiTask.Artifacts); l != 0 {
   802  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   803  		for k, ta := range apiTask.Artifacts {
   804  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   805  				GetterSource:  *ta.GetterSource,
   806  				GetterOptions: ta.GetterOptions,
   807  				GetterMode:    *ta.GetterMode,
   808  				RelativeDest:  *ta.RelativeDest,
   809  			}
   810  		}
   811  	}
   812  
   813  	if apiTask.Vault != nil {
   814  		structsTask.Vault = &structs.Vault{
   815  			Policies:     apiTask.Vault.Policies,
   816  			Env:          *apiTask.Vault.Env,
   817  			ChangeMode:   *apiTask.Vault.ChangeMode,
   818  			ChangeSignal: *apiTask.Vault.ChangeSignal,
   819  		}
   820  	}
   821  
   822  	if l := len(apiTask.Templates); l != 0 {
   823  		structsTask.Templates = make([]*structs.Template, l)
   824  		for i, template := range apiTask.Templates {
   825  			structsTask.Templates[i] = &structs.Template{
   826  				SourcePath:   *template.SourcePath,
   827  				DestPath:     *template.DestPath,
   828  				EmbeddedTmpl: *template.EmbeddedTmpl,
   829  				ChangeMode:   *template.ChangeMode,
   830  				ChangeSignal: *template.ChangeSignal,
   831  				Splay:        *template.Splay,
   832  				Perms:        *template.Perms,
   833  				LeftDelim:    *template.LeftDelim,
   834  				RightDelim:   *template.RightDelim,
   835  				Envvars:      *template.Envvars,
   836  				VaultGrace:   *template.VaultGrace,
   837  			}
   838  		}
   839  	}
   840  
   841  	if apiTask.DispatchPayload != nil {
   842  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
   843  			File: apiTask.DispatchPayload.File,
   844  		}
   845  	}
   846  }
   847  
   848  func ApiResourcesToStructs(in *api.Resources) *structs.Resources {
   849  	if in == nil {
   850  		return nil
   851  	}
   852  
   853  	out := &structs.Resources{
   854  		CPU:      *in.CPU,
   855  		MemoryMB: *in.MemoryMB,
   856  	}
   857  
   858  	// COMPAT(0.10): Only being used to issue warnings
   859  	if in.IOPS != nil {
   860  		out.IOPS = *in.IOPS
   861  	}
   862  
   863  	if l := len(in.Networks); l != 0 {
   864  		out.Networks = make([]*structs.NetworkResource, l)
   865  		for i, nw := range in.Networks {
   866  			out.Networks[i] = &structs.NetworkResource{
   867  				CIDR:  nw.CIDR,
   868  				IP:    nw.IP,
   869  				MBits: *nw.MBits,
   870  			}
   871  
   872  			if l := len(nw.DynamicPorts); l != 0 {
   873  				out.Networks[i].DynamicPorts = make([]structs.Port, l)
   874  				for j, dp := range nw.DynamicPorts {
   875  					out.Networks[i].DynamicPorts[j] = structs.Port{
   876  						Label: dp.Label,
   877  						Value: dp.Value,
   878  					}
   879  				}
   880  			}
   881  
   882  			if l := len(nw.ReservedPorts); l != 0 {
   883  				out.Networks[i].ReservedPorts = make([]structs.Port, l)
   884  				for j, rp := range nw.ReservedPorts {
   885  					out.Networks[i].ReservedPorts[j] = structs.Port{
   886  						Label: rp.Label,
   887  						Value: rp.Value,
   888  					}
   889  				}
   890  			}
   891  		}
   892  	}
   893  
   894  	if l := len(in.Devices); l != 0 {
   895  		out.Devices = make([]*structs.RequestedDevice, l)
   896  		for i, d := range in.Devices {
   897  			out.Devices[i] = &structs.RequestedDevice{
   898  				Name:        d.Name,
   899  				Count:       *d.Count,
   900  				Constraints: ApiConstraintsToStructs(d.Constraints),
   901  				Affinities:  ApiAffinitiesToStructs(d.Affinities),
   902  			}
   903  		}
   904  	}
   905  
   906  	return out
   907  }
   908  
   909  func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint {
   910  	if in == nil {
   911  		return nil
   912  	}
   913  
   914  	out := make([]*structs.Constraint, len(in))
   915  	for i, ac := range in {
   916  		out[i] = ApiConstraintToStructs(ac)
   917  	}
   918  
   919  	return out
   920  }
   921  
   922  func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint {
   923  	if in == nil {
   924  		return nil
   925  	}
   926  
   927  	return &structs.Constraint{
   928  		LTarget: in.LTarget,
   929  		RTarget: in.RTarget,
   930  		Operand: in.Operand,
   931  	}
   932  }
   933  
   934  func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity {
   935  	if in == nil {
   936  		return nil
   937  	}
   938  
   939  	out := make([]*structs.Affinity, len(in))
   940  	for i, ac := range in {
   941  		out[i] = ApiAffinityToStructs(ac)
   942  	}
   943  
   944  	return out
   945  }
   946  
   947  func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity {
   948  	return &structs.Affinity{
   949  		LTarget: a1.LTarget,
   950  		Operand: a1.Operand,
   951  		RTarget: a1.RTarget,
   952  		Weight:  *a1.Weight,
   953  	}
   954  }
   955  
   956  func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread {
   957  	ret := &structs.Spread{}
   958  	ret.Attribute = a1.Attribute
   959  	ret.Weight = *a1.Weight
   960  	if a1.SpreadTarget != nil {
   961  		ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget))
   962  		for i, st := range a1.SpreadTarget {
   963  			ret.SpreadTarget[i] = &structs.SpreadTarget{
   964  				Value:   st.Value,
   965  				Percent: st.Percent,
   966  			}
   967  		}
   968  	}
   969  	return ret
   970  }