github.com/smintz/nomad@v0.8.3/command/agent/job_endpoint.go (about)

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