github.com/bigcommerce/nomad@v0.9.3-bc/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  		All:   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  	all, _ := strconv.ParseBool(req.URL.Query().Get("all"))
   274  	args := structs.JobSpecificRequest{
   275  		JobID: jobName,
   276  		All:   all,
   277  	}
   278  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   279  		return nil, nil
   280  	}
   281  
   282  	var out structs.DeploymentListResponse
   283  	if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil {
   284  		return nil, err
   285  	}
   286  
   287  	setMeta(resp, &out.QueryMeta)
   288  	if out.Deployments == nil {
   289  		out.Deployments = make([]*structs.Deployment, 0)
   290  	}
   291  	return out.Deployments, nil
   292  }
   293  
   294  func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request,
   295  	jobName string) (interface{}, error) {
   296  	if req.Method != "GET" {
   297  		return nil, CodedError(405, ErrInvalidMethod)
   298  	}
   299  	args := structs.JobSpecificRequest{
   300  		JobID: jobName,
   301  	}
   302  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   303  		return nil, nil
   304  	}
   305  
   306  	var out structs.SingleDeploymentResponse
   307  	if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil {
   308  		return nil, err
   309  	}
   310  
   311  	setMeta(resp, &out.QueryMeta)
   312  	return out.Deployment, nil
   313  }
   314  
   315  func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request,
   316  	jobName string) (interface{}, error) {
   317  	switch req.Method {
   318  	case "GET":
   319  		return s.jobQuery(resp, req, jobName)
   320  	case "PUT", "POST":
   321  		return s.jobUpdate(resp, req, jobName)
   322  	case "DELETE":
   323  		return s.jobDelete(resp, req, jobName)
   324  	default:
   325  		return nil, CodedError(405, ErrInvalidMethod)
   326  	}
   327  }
   328  
   329  func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request,
   330  	jobName string) (interface{}, error) {
   331  	args := structs.JobSpecificRequest{
   332  		JobID: jobName,
   333  	}
   334  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   335  		return nil, nil
   336  	}
   337  
   338  	var out structs.SingleJobResponse
   339  	if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil {
   340  		return nil, err
   341  	}
   342  
   343  	setMeta(resp, &out.QueryMeta)
   344  	if out.Job == nil {
   345  		return nil, CodedError(404, "job not found")
   346  	}
   347  
   348  	// Decode the payload if there is any
   349  	job := out.Job
   350  	if len(job.Payload) != 0 {
   351  		decoded, err := snappy.Decode(nil, out.Job.Payload)
   352  		if err != nil {
   353  			return nil, err
   354  		}
   355  		job = job.Copy()
   356  		job.Payload = decoded
   357  	}
   358  
   359  	return job, nil
   360  }
   361  
   362  func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request,
   363  	jobName string) (interface{}, error) {
   364  	var args api.JobRegisterRequest
   365  	if err := decodeBody(req, &args); err != nil {
   366  		return nil, CodedError(400, err.Error())
   367  	}
   368  	if args.Job == nil {
   369  		return nil, CodedError(400, "Job must be specified")
   370  	}
   371  
   372  	if args.Job.ID == nil {
   373  		return nil, CodedError(400, "Job ID hasn't been provided")
   374  	}
   375  	if jobName != "" && *args.Job.ID != jobName {
   376  		return nil, CodedError(400, "Job ID does not match name")
   377  	}
   378  
   379  	sJob := ApiJobToStructJob(args.Job)
   380  
   381  	regReq := structs.JobRegisterRequest{
   382  		Job:            sJob,
   383  		EnforceIndex:   args.EnforceIndex,
   384  		JobModifyIndex: args.JobModifyIndex,
   385  		PolicyOverride: args.PolicyOverride,
   386  		WriteRequest: structs.WriteRequest{
   387  			Region:    args.WriteRequest.Region,
   388  			AuthToken: args.WriteRequest.SecretID,
   389  		},
   390  	}
   391  	s.parseWriteRequest(req, &regReq.WriteRequest)
   392  	regReq.Namespace = sJob.Namespace
   393  
   394  	var out structs.JobRegisterResponse
   395  	if err := s.agent.RPC("Job.Register", &regReq, &out); err != nil {
   396  		return nil, err
   397  	}
   398  	setIndex(resp, out.Index)
   399  	return out, nil
   400  }
   401  
   402  func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request,
   403  	jobName string) (interface{}, error) {
   404  
   405  	purgeStr := req.URL.Query().Get("purge")
   406  	var purgeBool bool
   407  	if purgeStr != "" {
   408  		var err error
   409  		purgeBool, err = strconv.ParseBool(purgeStr)
   410  		if err != nil {
   411  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err)
   412  		}
   413  	}
   414  
   415  	args := structs.JobDeregisterRequest{
   416  		JobID: jobName,
   417  		Purge: purgeBool,
   418  	}
   419  	s.parseWriteRequest(req, &args.WriteRequest)
   420  
   421  	var out structs.JobDeregisterResponse
   422  	if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil {
   423  		return nil, err
   424  	}
   425  	setIndex(resp, out.Index)
   426  	return out, nil
   427  }
   428  
   429  func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request,
   430  	jobName string) (interface{}, error) {
   431  
   432  	diffsStr := req.URL.Query().Get("diffs")
   433  	var diffsBool bool
   434  	if diffsStr != "" {
   435  		var err error
   436  		diffsBool, err = strconv.ParseBool(diffsStr)
   437  		if err != nil {
   438  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err)
   439  		}
   440  	}
   441  
   442  	args := structs.JobVersionsRequest{
   443  		JobID: jobName,
   444  		Diffs: diffsBool,
   445  	}
   446  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   447  		return nil, nil
   448  	}
   449  
   450  	var out structs.JobVersionsResponse
   451  	if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil {
   452  		return nil, err
   453  	}
   454  
   455  	setMeta(resp, &out.QueryMeta)
   456  	if len(out.Versions) == 0 {
   457  		return nil, CodedError(404, "job versions not found")
   458  	}
   459  
   460  	return out, nil
   461  }
   462  
   463  func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request,
   464  	jobName string) (interface{}, error) {
   465  
   466  	if req.Method != "PUT" && req.Method != "POST" {
   467  		return nil, CodedError(405, ErrInvalidMethod)
   468  	}
   469  
   470  	var revertRequest structs.JobRevertRequest
   471  	if err := decodeBody(req, &revertRequest); err != nil {
   472  		return nil, CodedError(400, err.Error())
   473  	}
   474  	if revertRequest.JobID == "" {
   475  		return nil, CodedError(400, "JobID must be specified")
   476  	}
   477  	if revertRequest.JobID != jobName {
   478  		return nil, CodedError(400, "Job ID does not match")
   479  	}
   480  
   481  	s.parseWriteRequest(req, &revertRequest.WriteRequest)
   482  
   483  	var out structs.JobRegisterResponse
   484  	if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil {
   485  		return nil, err
   486  	}
   487  
   488  	setMeta(resp, &out.QueryMeta)
   489  	return out, nil
   490  }
   491  
   492  func (s *HTTPServer) jobStable(resp http.ResponseWriter, req *http.Request,
   493  	jobName string) (interface{}, error) {
   494  
   495  	if req.Method != "PUT" && req.Method != "POST" {
   496  		return nil, CodedError(405, ErrInvalidMethod)
   497  	}
   498  
   499  	var stableRequest structs.JobStabilityRequest
   500  	if err := decodeBody(req, &stableRequest); err != nil {
   501  		return nil, CodedError(400, err.Error())
   502  	}
   503  	if stableRequest.JobID == "" {
   504  		return nil, CodedError(400, "JobID must be specified")
   505  	}
   506  	if stableRequest.JobID != jobName {
   507  		return nil, CodedError(400, "Job ID does not match")
   508  	}
   509  
   510  	s.parseWriteRequest(req, &stableRequest.WriteRequest)
   511  
   512  	var out structs.JobStabilityResponse
   513  	if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil {
   514  		return nil, err
   515  	}
   516  
   517  	setIndex(resp, out.Index)
   518  	return out, nil
   519  }
   520  
   521  func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   522  	args := structs.JobSummaryRequest{
   523  		JobID: name,
   524  	}
   525  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   526  		return nil, nil
   527  	}
   528  
   529  	var out structs.JobSummaryResponse
   530  	if err := s.agent.RPC("Job.Summary", &args, &out); err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	setMeta(resp, &out.QueryMeta)
   535  	if out.JobSummary == nil {
   536  		return nil, CodedError(404, "job not found")
   537  	}
   538  	setIndex(resp, out.Index)
   539  	return out.JobSummary, nil
   540  }
   541  
   542  func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   543  	if req.Method != "PUT" && req.Method != "POST" {
   544  		return nil, CodedError(405, ErrInvalidMethod)
   545  	}
   546  	args := structs.JobDispatchRequest{}
   547  	if err := decodeBody(req, &args); err != nil {
   548  		return nil, CodedError(400, err.Error())
   549  	}
   550  	if args.JobID != "" && args.JobID != name {
   551  		return nil, CodedError(400, "Job ID does not match")
   552  	}
   553  	if args.JobID == "" {
   554  		args.JobID = name
   555  	}
   556  
   557  	s.parseWriteRequest(req, &args.WriteRequest)
   558  
   559  	var out structs.JobDispatchResponse
   560  	if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil {
   561  		return nil, err
   562  	}
   563  	setIndex(resp, out.Index)
   564  	return out, nil
   565  }
   566  
   567  // JobsParseRequest parses a hcl jobspec and returns a api.Job
   568  func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   569  	if req.Method != http.MethodPut && req.Method != http.MethodPost {
   570  		return nil, CodedError(405, ErrInvalidMethod)
   571  	}
   572  
   573  	args := &api.JobsParseRequest{}
   574  	if err := decodeBody(req, &args); err != nil {
   575  		return nil, CodedError(400, err.Error())
   576  	}
   577  	if args.JobHCL == "" {
   578  		return nil, CodedError(400, "Job spec is empty")
   579  	}
   580  
   581  	jobfile := strings.NewReader(args.JobHCL)
   582  	jobStruct, err := jobspec.Parse(jobfile)
   583  	if err != nil {
   584  		return nil, CodedError(400, err.Error())
   585  	}
   586  
   587  	if args.Canonicalize {
   588  		jobStruct.Canonicalize()
   589  	}
   590  	return jobStruct, nil
   591  }
   592  
   593  func ApiJobToStructJob(job *api.Job) *structs.Job {
   594  	job.Canonicalize()
   595  
   596  	j := &structs.Job{
   597  		Stop:        *job.Stop,
   598  		Region:      *job.Region,
   599  		Namespace:   *job.Namespace,
   600  		ID:          *job.ID,
   601  		ParentID:    *job.ParentID,
   602  		Name:        *job.Name,
   603  		Type:        *job.Type,
   604  		Priority:    *job.Priority,
   605  		AllAtOnce:   *job.AllAtOnce,
   606  		Datacenters: job.Datacenters,
   607  		Payload:     job.Payload,
   608  		Meta:        job.Meta,
   609  		VaultToken:  *job.VaultToken,
   610  		Constraints: ApiConstraintsToStructs(job.Constraints),
   611  		Affinities:  ApiAffinitiesToStructs(job.Affinities),
   612  	}
   613  
   614  	// Update has been pushed into the task groups. stagger and max_parallel are
   615  	// preserved at the job level, but all other values are discarded. The job.Update
   616  	// api value is merged into TaskGroups already in api.Canonicalize
   617  	if job.Update != nil {
   618  		j.Update = structs.UpdateStrategy{}
   619  
   620  		if job.Update.Stagger != nil {
   621  			j.Update.Stagger = *job.Update.Stagger
   622  		}
   623  		if job.Update.MaxParallel != nil {
   624  			j.Update.MaxParallel = *job.Update.MaxParallel
   625  		}
   626  	}
   627  
   628  	if l := len(job.Spreads); l != 0 {
   629  		j.Spreads = make([]*structs.Spread, l)
   630  		for i, apiSpread := range job.Spreads {
   631  			j.Spreads[i] = ApiSpreadToStructs(apiSpread)
   632  		}
   633  	}
   634  
   635  	if job.Periodic != nil {
   636  		j.Periodic = &structs.PeriodicConfig{
   637  			Enabled:         *job.Periodic.Enabled,
   638  			SpecType:        *job.Periodic.SpecType,
   639  			ProhibitOverlap: *job.Periodic.ProhibitOverlap,
   640  			TimeZone:        *job.Periodic.TimeZone,
   641  		}
   642  
   643  		if job.Periodic.Spec != nil {
   644  			j.Periodic.Spec = *job.Periodic.Spec
   645  		}
   646  	}
   647  
   648  	if job.ParameterizedJob != nil {
   649  		j.ParameterizedJob = &structs.ParameterizedJobConfig{
   650  			Payload:      job.ParameterizedJob.Payload,
   651  			MetaRequired: job.ParameterizedJob.MetaRequired,
   652  			MetaOptional: job.ParameterizedJob.MetaOptional,
   653  		}
   654  	}
   655  
   656  	if l := len(job.TaskGroups); l != 0 {
   657  		j.TaskGroups = make([]*structs.TaskGroup, l)
   658  		for i, taskGroup := range job.TaskGroups {
   659  			tg := &structs.TaskGroup{}
   660  			ApiTgToStructsTG(taskGroup, tg)
   661  			j.TaskGroups[i] = tg
   662  		}
   663  	}
   664  
   665  	return j
   666  }
   667  
   668  func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
   669  	tg.Name = *taskGroup.Name
   670  	tg.Count = *taskGroup.Count
   671  	tg.Meta = taskGroup.Meta
   672  	tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints)
   673  	tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities)
   674  
   675  	tg.RestartPolicy = &structs.RestartPolicy{
   676  		Attempts: *taskGroup.RestartPolicy.Attempts,
   677  		Interval: *taskGroup.RestartPolicy.Interval,
   678  		Delay:    *taskGroup.RestartPolicy.Delay,
   679  		Mode:     *taskGroup.RestartPolicy.Mode,
   680  	}
   681  
   682  	if taskGroup.ReschedulePolicy != nil {
   683  		tg.ReschedulePolicy = &structs.ReschedulePolicy{
   684  			Attempts:      *taskGroup.ReschedulePolicy.Attempts,
   685  			Interval:      *taskGroup.ReschedulePolicy.Interval,
   686  			Delay:         *taskGroup.ReschedulePolicy.Delay,
   687  			DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction,
   688  			MaxDelay:      *taskGroup.ReschedulePolicy.MaxDelay,
   689  			Unlimited:     *taskGroup.ReschedulePolicy.Unlimited,
   690  		}
   691  	}
   692  
   693  	if taskGroup.Migrate != nil {
   694  		tg.Migrate = &structs.MigrateStrategy{
   695  			MaxParallel:     *taskGroup.Migrate.MaxParallel,
   696  			HealthCheck:     *taskGroup.Migrate.HealthCheck,
   697  			MinHealthyTime:  *taskGroup.Migrate.MinHealthyTime,
   698  			HealthyDeadline: *taskGroup.Migrate.HealthyDeadline,
   699  		}
   700  	}
   701  
   702  	tg.EphemeralDisk = &structs.EphemeralDisk{
   703  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   704  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   705  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   706  	}
   707  
   708  	if l := len(taskGroup.Spreads); l != 0 {
   709  		tg.Spreads = make([]*structs.Spread, l)
   710  		for k, spread := range taskGroup.Spreads {
   711  			tg.Spreads[k] = ApiSpreadToStructs(spread)
   712  		}
   713  	}
   714  
   715  	if taskGroup.Update != nil {
   716  		tg.Update = &structs.UpdateStrategy{
   717  			Stagger:          *taskGroup.Update.Stagger,
   718  			MaxParallel:      *taskGroup.Update.MaxParallel,
   719  			HealthCheck:      *taskGroup.Update.HealthCheck,
   720  			MinHealthyTime:   *taskGroup.Update.MinHealthyTime,
   721  			HealthyDeadline:  *taskGroup.Update.HealthyDeadline,
   722  			ProgressDeadline: *taskGroup.Update.ProgressDeadline,
   723  			Canary:           *taskGroup.Update.Canary,
   724  		}
   725  
   726  		// boolPtr fields may be nil, others will have pointers to default values via Canonicalize
   727  		if taskGroup.Update.AutoRevert != nil {
   728  			tg.Update.AutoRevert = *taskGroup.Update.AutoRevert
   729  		}
   730  
   731  		if taskGroup.Update.AutoPromote != nil {
   732  			tg.Update.AutoPromote = *taskGroup.Update.AutoPromote
   733  		}
   734  	}
   735  
   736  	if l := len(taskGroup.Tasks); l != 0 {
   737  		tg.Tasks = make([]*structs.Task, l)
   738  		for l, task := range taskGroup.Tasks {
   739  			t := &structs.Task{}
   740  			ApiTaskToStructsTask(task, t)
   741  			tg.Tasks[l] = t
   742  		}
   743  	}
   744  }
   745  
   746  // ApiTaskToStructsTask is a copy and type conversion between the API
   747  // representation of a task from a struct representation of a task.
   748  func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
   749  	structsTask.Name = apiTask.Name
   750  	structsTask.Driver = apiTask.Driver
   751  	structsTask.User = apiTask.User
   752  	structsTask.Leader = apiTask.Leader
   753  	structsTask.Config = apiTask.Config
   754  	structsTask.Env = apiTask.Env
   755  	structsTask.Meta = apiTask.Meta
   756  	structsTask.KillTimeout = *apiTask.KillTimeout
   757  	structsTask.ShutdownDelay = apiTask.ShutdownDelay
   758  	structsTask.KillSignal = apiTask.KillSignal
   759  	structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints)
   760  	structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities)
   761  
   762  	if l := len(apiTask.Services); l != 0 {
   763  		structsTask.Services = make([]*structs.Service, l)
   764  		for i, service := range apiTask.Services {
   765  			structsTask.Services[i] = &structs.Service{
   766  				Name:        service.Name,
   767  				PortLabel:   service.PortLabel,
   768  				Tags:        service.Tags,
   769  				CanaryTags:  service.CanaryTags,
   770  				AddressMode: service.AddressMode,
   771  			}
   772  
   773  			if l := len(service.Checks); l != 0 {
   774  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   775  				for j, check := range service.Checks {
   776  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   777  						Name:          check.Name,
   778  						Type:          check.Type,
   779  						Command:       check.Command,
   780  						Args:          check.Args,
   781  						Path:          check.Path,
   782  						Protocol:      check.Protocol,
   783  						PortLabel:     check.PortLabel,
   784  						AddressMode:   check.AddressMode,
   785  						Interval:      check.Interval,
   786  						Timeout:       check.Timeout,
   787  						InitialStatus: check.InitialStatus,
   788  						TLSSkipVerify: check.TLSSkipVerify,
   789  						Header:        check.Header,
   790  						Method:        check.Method,
   791  						GRPCService:   check.GRPCService,
   792  						GRPCUseTLS:    check.GRPCUseTLS,
   793  					}
   794  					if check.CheckRestart != nil {
   795  						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
   796  							Limit:          check.CheckRestart.Limit,
   797  							Grace:          *check.CheckRestart.Grace,
   798  							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
   799  						}
   800  					}
   801  				}
   802  			}
   803  		}
   804  	}
   805  
   806  	structsTask.Resources = ApiResourcesToStructs(apiTask.Resources)
   807  
   808  	structsTask.LogConfig = &structs.LogConfig{
   809  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   810  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   811  	}
   812  
   813  	if l := len(apiTask.Artifacts); l != 0 {
   814  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   815  		for k, ta := range apiTask.Artifacts {
   816  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   817  				GetterSource:  *ta.GetterSource,
   818  				GetterOptions: ta.GetterOptions,
   819  				GetterMode:    *ta.GetterMode,
   820  				RelativeDest:  *ta.RelativeDest,
   821  			}
   822  		}
   823  	}
   824  
   825  	if apiTask.Vault != nil {
   826  		structsTask.Vault = &structs.Vault{
   827  			Policies:     apiTask.Vault.Policies,
   828  			Env:          *apiTask.Vault.Env,
   829  			ChangeMode:   *apiTask.Vault.ChangeMode,
   830  			ChangeSignal: *apiTask.Vault.ChangeSignal,
   831  		}
   832  	}
   833  
   834  	if l := len(apiTask.Templates); l != 0 {
   835  		structsTask.Templates = make([]*structs.Template, l)
   836  		for i, template := range apiTask.Templates {
   837  			structsTask.Templates[i] = &structs.Template{
   838  				SourcePath:   *template.SourcePath,
   839  				DestPath:     *template.DestPath,
   840  				EmbeddedTmpl: *template.EmbeddedTmpl,
   841  				ChangeMode:   *template.ChangeMode,
   842  				ChangeSignal: *template.ChangeSignal,
   843  				Splay:        *template.Splay,
   844  				Perms:        *template.Perms,
   845  				LeftDelim:    *template.LeftDelim,
   846  				RightDelim:   *template.RightDelim,
   847  				Envvars:      *template.Envvars,
   848  				VaultGrace:   *template.VaultGrace,
   849  			}
   850  		}
   851  	}
   852  
   853  	if apiTask.DispatchPayload != nil {
   854  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
   855  			File: apiTask.DispatchPayload.File,
   856  		}
   857  	}
   858  }
   859  
   860  func ApiResourcesToStructs(in *api.Resources) *structs.Resources {
   861  	if in == nil {
   862  		return nil
   863  	}
   864  
   865  	out := &structs.Resources{
   866  		CPU:      *in.CPU,
   867  		MemoryMB: *in.MemoryMB,
   868  	}
   869  
   870  	// COMPAT(0.10): Only being used to issue warnings
   871  	if in.IOPS != nil {
   872  		out.IOPS = *in.IOPS
   873  	}
   874  
   875  	if l := len(in.Networks); l != 0 {
   876  		out.Networks = make([]*structs.NetworkResource, l)
   877  		for i, nw := range in.Networks {
   878  			out.Networks[i] = &structs.NetworkResource{
   879  				CIDR:  nw.CIDR,
   880  				IP:    nw.IP,
   881  				MBits: *nw.MBits,
   882  			}
   883  
   884  			if l := len(nw.DynamicPorts); l != 0 {
   885  				out.Networks[i].DynamicPorts = make([]structs.Port, l)
   886  				for j, dp := range nw.DynamicPorts {
   887  					out.Networks[i].DynamicPorts[j] = structs.Port{
   888  						Label: dp.Label,
   889  						Value: dp.Value,
   890  					}
   891  				}
   892  			}
   893  
   894  			if l := len(nw.ReservedPorts); l != 0 {
   895  				out.Networks[i].ReservedPorts = make([]structs.Port, l)
   896  				for j, rp := range nw.ReservedPorts {
   897  					out.Networks[i].ReservedPorts[j] = structs.Port{
   898  						Label: rp.Label,
   899  						Value: rp.Value,
   900  					}
   901  				}
   902  			}
   903  		}
   904  	}
   905  
   906  	if l := len(in.Devices); l != 0 {
   907  		out.Devices = make([]*structs.RequestedDevice, l)
   908  		for i, d := range in.Devices {
   909  			out.Devices[i] = &structs.RequestedDevice{
   910  				Name:        d.Name,
   911  				Count:       *d.Count,
   912  				Constraints: ApiConstraintsToStructs(d.Constraints),
   913  				Affinities:  ApiAffinitiesToStructs(d.Affinities),
   914  			}
   915  		}
   916  	}
   917  
   918  	return out
   919  }
   920  
   921  func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint {
   922  	if in == nil {
   923  		return nil
   924  	}
   925  
   926  	out := make([]*structs.Constraint, len(in))
   927  	for i, ac := range in {
   928  		out[i] = ApiConstraintToStructs(ac)
   929  	}
   930  
   931  	return out
   932  }
   933  
   934  func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint {
   935  	if in == nil {
   936  		return nil
   937  	}
   938  
   939  	return &structs.Constraint{
   940  		LTarget: in.LTarget,
   941  		RTarget: in.RTarget,
   942  		Operand: in.Operand,
   943  	}
   944  }
   945  
   946  func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity {
   947  	if in == nil {
   948  		return nil
   949  	}
   950  
   951  	out := make([]*structs.Affinity, len(in))
   952  	for i, ac := range in {
   953  		out[i] = ApiAffinityToStructs(ac)
   954  	}
   955  
   956  	return out
   957  }
   958  
   959  func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity {
   960  	return &structs.Affinity{
   961  		LTarget: a1.LTarget,
   962  		Operand: a1.Operand,
   963  		RTarget: a1.RTarget,
   964  		Weight:  *a1.Weight,
   965  	}
   966  }
   967  
   968  func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread {
   969  	ret := &structs.Spread{}
   970  	ret.Attribute = a1.Attribute
   971  	ret.Weight = *a1.Weight
   972  	if a1.SpreadTarget != nil {
   973  		ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget))
   974  		for i, st := range a1.SpreadTarget {
   975  			ret.SpreadTarget[i] = &structs.SpreadTarget{
   976  				Value:   st.Value,
   977  				Percent: st.Percent,
   978  			}
   979  		}
   980  	}
   981  	return ret
   982  }