github.com/hashicorp/nomad/api@v0.0.0-20240306165712-3193ac204f65/jobs.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package api
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net/url"
    12  	"sort"
    13  	"strconv"
    14  	"time"
    15  
    16  	"github.com/hashicorp/cronexpr"
    17  	"golang.org/x/exp/maps"
    18  )
    19  
    20  const (
    21  	// JobTypeService indicates a long-running processes
    22  	JobTypeService = "service"
    23  
    24  	// JobTypeBatch indicates a short-lived process
    25  	JobTypeBatch = "batch"
    26  
    27  	// JobTypeSystem indicates a system process that should run on all clients
    28  	JobTypeSystem = "system"
    29  
    30  	// JobTypeSysbatch indicates a short-lived system process that should run
    31  	// on all clients.
    32  	JobTypeSysbatch = "sysbatch"
    33  
    34  	// JobDefaultPriority is the default priority if not specified.
    35  	JobDefaultPriority = 50
    36  
    37  	// PeriodicSpecCron is used for a cron spec.
    38  	PeriodicSpecCron = "cron"
    39  
    40  	// DefaultNamespace is the default namespace.
    41  	DefaultNamespace = "default"
    42  
    43  	// For Job configuration, GlobalRegion is a sentinel region value
    44  	// that users may specify to indicate the job should be run on
    45  	// the region of the node that the job was submitted to.
    46  	// For Client configuration, if no region information is given,
    47  	// the client node will default to be part of the GlobalRegion.
    48  	GlobalRegion = "global"
    49  )
    50  
    51  const (
    52  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    53  	// enforcing the job modify index during registers.
    54  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    55  )
    56  
    57  const (
    58  	// JobPeriodicLaunchSuffix is the string appended to the periodic jobs ID
    59  	// when launching derived instances of it.
    60  	JobPeriodicLaunchSuffix = "/periodic-"
    61  
    62  	// JobDispatchLaunchSuffix is the string appended to the parameterized job's ID
    63  	// when dispatching instances of it.
    64  	JobDispatchLaunchSuffix = "/dispatch-"
    65  )
    66  
    67  // Jobs is used to access the job-specific endpoints.
    68  type Jobs struct {
    69  	client *Client
    70  }
    71  
    72  // JobsParseRequest is used for arguments of the /v1/jobs/parse endpoint
    73  type JobsParseRequest struct {
    74  	// JobHCL is an hcl jobspec
    75  	JobHCL string
    76  
    77  	// HCLv1 indicates whether the JobHCL should be parsed with the hcl v1 parser
    78  	HCLv1 bool `json:"hclv1,omitempty"`
    79  
    80  	// Variables are HCL2 variables associated with the job. Only works with hcl2.
    81  	//
    82  	// Interpreted as if it were the content of a variables file.
    83  	Variables string
    84  
    85  	// Canonicalize is a flag as to if the server should return default values
    86  	// for unset fields
    87  	Canonicalize bool
    88  }
    89  
    90  // Jobs returns a handle on the jobs endpoints.
    91  func (c *Client) Jobs() *Jobs {
    92  	return &Jobs{client: c}
    93  }
    94  
    95  // ParseHCL is used to convert the HCL representation of a Job to JSON server side.
    96  // To parse the HCL client side see package github.com/hashicorp/nomad/jobspec
    97  // Use ParseHCLOpts if you need to customize JobsParseRequest.
    98  func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) {
    99  	req := &JobsParseRequest{
   100  		JobHCL:       jobHCL,
   101  		Canonicalize: canonicalize,
   102  	}
   103  	return j.ParseHCLOpts(req)
   104  }
   105  
   106  // ParseHCLOpts is used to request the server convert the HCL representation of a
   107  // Job to JSON on our behalf. Accepts HCL1 or HCL2 jobs as input.
   108  func (j *Jobs) ParseHCLOpts(req *JobsParseRequest) (*Job, error) {
   109  	var job Job
   110  	_, err := j.client.put("/v1/jobs/parse", req, &job, nil)
   111  	return &job, err
   112  }
   113  
   114  func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) {
   115  	var resp JobValidateResponse
   116  	req := &JobValidateRequest{Job: job}
   117  	if q != nil {
   118  		req.WriteRequest = WriteRequest{Region: q.Region}
   119  	}
   120  	wm, err := j.client.put("/v1/validate/job", req, &resp, q)
   121  	return &resp, wm, err
   122  }
   123  
   124  // RegisterOptions is used to pass through job registration parameters
   125  type RegisterOptions struct {
   126  	EnforceIndex   bool
   127  	ModifyIndex    uint64
   128  	PolicyOverride bool
   129  	PreserveCounts bool
   130  	EvalPriority   int
   131  	Submission     *JobSubmission
   132  }
   133  
   134  // Register is used to register a new job. It returns the ID
   135  // of the evaluation, along with any errors encountered.
   136  func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   137  	return j.RegisterOpts(job, nil, q)
   138  }
   139  
   140  // EnforceRegister is used to register a job enforcing its job modify index.
   141  func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   142  	opts := RegisterOptions{EnforceIndex: true, ModifyIndex: modifyIndex}
   143  	return j.RegisterOpts(job, &opts, q)
   144  }
   145  
   146  // RegisterOpts is used to register a new job with the passed RegisterOpts. It
   147  // returns the ID of the evaluation, along with any errors encountered.
   148  func (j *Jobs) RegisterOpts(job *Job, opts *RegisterOptions, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   149  	// Format the request
   150  	req := &JobRegisterRequest{Job: job}
   151  	if opts != nil {
   152  		if opts.EnforceIndex {
   153  			req.EnforceIndex = true
   154  			req.JobModifyIndex = opts.ModifyIndex
   155  		}
   156  		req.PolicyOverride = opts.PolicyOverride
   157  		req.PreserveCounts = opts.PreserveCounts
   158  		req.EvalPriority = opts.EvalPriority
   159  		req.Submission = opts.Submission
   160  	}
   161  
   162  	var resp JobRegisterResponse
   163  	wm, err := j.client.put("/v1/jobs", req, &resp, q)
   164  	if err != nil {
   165  		return nil, nil, err
   166  	}
   167  	return &resp, wm, nil
   168  }
   169  
   170  type JobListFields struct {
   171  	Meta bool
   172  }
   173  type JobListOptions struct {
   174  	Fields *JobListFields
   175  }
   176  
   177  // List is used to list all of the existing jobs.
   178  func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
   179  	return j.ListOptions(nil, q)
   180  }
   181  
   182  // List is used to list all of the existing jobs.
   183  func (j *Jobs) ListOptions(opts *JobListOptions, q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
   184  	var resp []*JobListStub
   185  
   186  	destinationURL := "/v1/jobs"
   187  
   188  	if opts != nil && opts.Fields != nil {
   189  		qp := url.Values{}
   190  		qp.Add("meta", fmt.Sprint(opts.Fields.Meta))
   191  		destinationURL = destinationURL + "?" + qp.Encode()
   192  	}
   193  
   194  	qm, err := j.client.query(destinationURL, &resp, q)
   195  	if err != nil {
   196  		return nil, qm, err
   197  	}
   198  	sort.Sort(JobIDSort(resp))
   199  	return resp, qm, nil
   200  }
   201  
   202  // PrefixList is used to list all existing jobs that match the prefix.
   203  func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
   204  	return j.List(&QueryOptions{Prefix: prefix})
   205  }
   206  
   207  // Info is used to retrieve information about a particular
   208  // job given its unique ID.
   209  func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
   210  	var resp Job
   211  	qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID), &resp, q)
   212  	if err != nil {
   213  		return nil, nil, err
   214  	}
   215  	return &resp, qm, nil
   216  }
   217  
   218  // Scale is used to retrieve information about a particular
   219  // job given its unique ID.
   220  func (j *Jobs) Scale(jobID, group string, count *int, message string, error bool, meta map[string]interface{},
   221  	q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   222  
   223  	var count64 *int64
   224  	if count != nil {
   225  		count64 = pointerOf(int64(*count))
   226  	}
   227  	req := &ScalingRequest{
   228  		Count: count64,
   229  		Target: map[string]string{
   230  			"Job":   jobID,
   231  			"Group": group,
   232  		},
   233  		Error:   error,
   234  		Message: message,
   235  		Meta:    meta,
   236  	}
   237  	var resp JobRegisterResponse
   238  	qm, err := j.client.put(fmt.Sprintf("/v1/job/%s/scale", url.PathEscape(jobID)), req, &resp, q)
   239  	if err != nil {
   240  		return nil, nil, err
   241  	}
   242  	return &resp, qm, nil
   243  }
   244  
   245  // ScaleStatus is used to retrieve information about a particular
   246  // job given its unique ID.
   247  func (j *Jobs) ScaleStatus(jobID string, q *QueryOptions) (*JobScaleStatusResponse, *QueryMeta, error) {
   248  	var resp JobScaleStatusResponse
   249  	qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/scale", url.PathEscape(jobID)), &resp, q)
   250  	if err != nil {
   251  		return nil, nil, err
   252  	}
   253  	return &resp, qm, nil
   254  }
   255  
   256  // Versions is used to retrieve all versions of a particular job given its
   257  // unique ID.
   258  func (j *Jobs) Versions(jobID string, diffs bool, q *QueryOptions) ([]*Job, []*JobDiff, *QueryMeta, error) {
   259  	var resp JobVersionsResponse
   260  	qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/versions?diffs=%v", url.PathEscape(jobID), diffs), &resp, q)
   261  	if err != nil {
   262  		return nil, nil, nil, err
   263  	}
   264  	return resp.Versions, resp.Diffs, qm, nil
   265  }
   266  
   267  // Submission is used to retrieve the original submitted source of a job given its
   268  // namespace, jobID, and version number. The original source might not be available,
   269  // which case nil is returned with no error.
   270  func (j *Jobs) Submission(jobID string, version int, q *QueryOptions) (*JobSubmission, *QueryMeta, error) {
   271  	var sub JobSubmission
   272  	s := fmt.Sprintf("/v1/job/%s/submission?version=%d", url.PathEscape(jobID), version)
   273  	qm, err := j.client.query(s, &sub, q)
   274  	if err != nil {
   275  		return nil, nil, err
   276  	}
   277  	return &sub, qm, nil
   278  }
   279  
   280  // Allocations is used to return the allocs for a given job ID.
   281  func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
   282  	var resp []*AllocationListStub
   283  	u, err := url.Parse("/v1/job/" + url.PathEscape(jobID) + "/allocations")
   284  	if err != nil {
   285  		return nil, nil, err
   286  	}
   287  
   288  	v := u.Query()
   289  	v.Add("all", strconv.FormatBool(allAllocs))
   290  	u.RawQuery = v.Encode()
   291  
   292  	qm, err := j.client.query(u.String(), &resp, q)
   293  	if err != nil {
   294  		return nil, nil, err
   295  	}
   296  	sort.Sort(AllocIndexSort(resp))
   297  	return resp, qm, nil
   298  }
   299  
   300  // Deployments is used to query the deployments associated with the given job
   301  // ID.
   302  func (j *Jobs) Deployments(jobID string, all bool, q *QueryOptions) ([]*Deployment, *QueryMeta, error) {
   303  	var resp []*Deployment
   304  	u, err := url.Parse("/v1/job/" + url.PathEscape(jobID) + "/deployments")
   305  	if err != nil {
   306  		return nil, nil, err
   307  	}
   308  
   309  	v := u.Query()
   310  	v.Add("all", strconv.FormatBool(all))
   311  	u.RawQuery = v.Encode()
   312  	qm, err := j.client.query(u.String(), &resp, q)
   313  	if err != nil {
   314  		return nil, nil, err
   315  	}
   316  	sort.Sort(DeploymentIndexSort(resp))
   317  	return resp, qm, nil
   318  }
   319  
   320  // LatestDeployment is used to query for the latest deployment associated with
   321  // the given job ID.
   322  func (j *Jobs) LatestDeployment(jobID string, q *QueryOptions) (*Deployment, *QueryMeta, error) {
   323  	var resp *Deployment
   324  	qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/deployment", &resp, q)
   325  	if err != nil {
   326  		return nil, nil, err
   327  	}
   328  	return resp, qm, nil
   329  }
   330  
   331  // Evaluations is used to query the evaluations associated with the given job
   332  // ID.
   333  func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
   334  	var resp []*Evaluation
   335  	qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/evaluations", &resp, q)
   336  	if err != nil {
   337  		return nil, nil, err
   338  	}
   339  	sort.Sort(EvalIndexSort(resp))
   340  	return resp, qm, nil
   341  }
   342  
   343  // Deregister is used to remove an existing job. If purge is set to true, the job
   344  // is deregistered and purged from the system versus still being queryable and
   345  // eventually GC'ed from the system. Most callers should not specify purge.
   346  func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) {
   347  	var resp JobDeregisterResponse
   348  	wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", url.PathEscape(jobID), purge), nil, &resp, q)
   349  	if err != nil {
   350  		return "", nil, err
   351  	}
   352  	return resp.EvalID, wm, nil
   353  }
   354  
   355  // DeregisterOptions is used to pass through job deregistration parameters
   356  type DeregisterOptions struct {
   357  	// If Purge is set to true, the job is deregistered and purged from the
   358  	// system versus still being queryable and eventually GC'ed from the
   359  	// system. Most callers should not specify purge.
   360  	Purge bool
   361  
   362  	// If Global is set to true, all regions of a multiregion job will be
   363  	// stopped.
   364  	Global bool
   365  
   366  	// EvalPriority is an optional priority to use on any evaluation created as
   367  	// a result on this job deregistration. This value must be between 1-100
   368  	// inclusively, where a larger value corresponds to a higher priority. This
   369  	// is useful when an operator wishes to push through a job deregistration
   370  	// in busy clusters with a large evaluation backlog.
   371  	EvalPriority int
   372  
   373  	// NoShutdownDelay, if set to true, will override the group and
   374  	// task shutdown_delay configuration and ignore the delay for any
   375  	// allocations stopped as a result of this Deregister call.
   376  	NoShutdownDelay bool
   377  }
   378  
   379  // DeregisterOpts is used to remove an existing job. See DeregisterOptions
   380  // for parameters.
   381  func (j *Jobs) DeregisterOpts(jobID string, opts *DeregisterOptions, q *WriteOptions) (string, *WriteMeta, error) {
   382  	var resp JobDeregisterResponse
   383  
   384  	// The base endpoint to add query params to.
   385  	endpoint := "/v1/job/" + url.PathEscape(jobID)
   386  
   387  	// Protect against nil opts. url.Values expects a string, and so using
   388  	// fmt.Sprintf is the best way to do this.
   389  	if opts != nil {
   390  		endpoint += fmt.Sprintf("?purge=%t&global=%t&eval_priority=%v&no_shutdown_delay=%t",
   391  			opts.Purge, opts.Global, opts.EvalPriority, opts.NoShutdownDelay)
   392  	}
   393  
   394  	wm, err := j.client.delete(endpoint, nil, &resp, q)
   395  	if err != nil {
   396  		return "", nil, err
   397  	}
   398  	return resp.EvalID, wm, nil
   399  }
   400  
   401  // ForceEvaluate is used to force-evaluate an existing job.
   402  func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   403  	var resp JobRegisterResponse
   404  	wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/evaluate", nil, &resp, q)
   405  	if err != nil {
   406  		return "", nil, err
   407  	}
   408  	return resp.EvalID, wm, nil
   409  }
   410  
   411  // EvaluateWithOpts is used to force-evaluate an existing job and takes additional options
   412  // for whether to force reschedule failed allocations
   413  func (j *Jobs) EvaluateWithOpts(jobID string, opts EvalOptions, q *WriteOptions) (string, *WriteMeta, error) {
   414  	req := &JobEvaluateRequest{
   415  		JobID:       jobID,
   416  		EvalOptions: opts,
   417  	}
   418  
   419  	var resp JobRegisterResponse
   420  	wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/evaluate", req, &resp, q)
   421  	if err != nil {
   422  		return "", nil, err
   423  	}
   424  	return resp.EvalID, wm, nil
   425  }
   426  
   427  // PeriodicForce spawns a new instance of the periodic job and returns the eval ID
   428  func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   429  	var resp periodicForceResponse
   430  	wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/periodic/force", nil, &resp, q)
   431  	if err != nil {
   432  		return "", nil, err
   433  	}
   434  	return resp.EvalID, wm, nil
   435  }
   436  
   437  // PlanOptions is used to pass through job planning parameters
   438  type PlanOptions struct {
   439  	Diff           bool
   440  	PolicyOverride bool
   441  }
   442  
   443  func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   444  	opts := PlanOptions{Diff: diff}
   445  	return j.PlanOpts(job, &opts, q)
   446  }
   447  
   448  func (j *Jobs) PlanOpts(job *Job, opts *PlanOptions, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   449  	if job == nil {
   450  		return nil, nil, errors.New("must pass non-nil job")
   451  	}
   452  	if job.ID == nil {
   453  		return nil, nil, errors.New("job is missing ID")
   454  	}
   455  
   456  	// Setup the request
   457  	req := &JobPlanRequest{
   458  		Job: job,
   459  	}
   460  	if opts != nil {
   461  		req.Diff = opts.Diff
   462  		req.PolicyOverride = opts.PolicyOverride
   463  	}
   464  
   465  	var resp JobPlanResponse
   466  	wm, err := j.client.put("/v1/job/"+url.PathEscape(*job.ID)+"/plan", req, &resp, q)
   467  	if err != nil {
   468  		return nil, nil, err
   469  	}
   470  	return &resp, wm, nil
   471  }
   472  
   473  func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) {
   474  	var resp JobSummary
   475  	qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/summary", &resp, q)
   476  	if err != nil {
   477  		return nil, nil, err
   478  	}
   479  	return &resp, qm, nil
   480  }
   481  
   482  func (j *Jobs) Dispatch(jobID string, meta map[string]string,
   483  	payload []byte, idPrefixTemplate string, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) {
   484  	var resp JobDispatchResponse
   485  	req := &JobDispatchRequest{
   486  		JobID:            jobID,
   487  		Meta:             meta,
   488  		Payload:          payload,
   489  		IdPrefixTemplate: idPrefixTemplate,
   490  	}
   491  	wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/dispatch", req, &resp, q)
   492  	if err != nil {
   493  		return nil, nil, err
   494  	}
   495  	return &resp, wm, nil
   496  }
   497  
   498  // Revert is used to revert the given job to the passed version. If
   499  // enforceVersion is set, the job is only reverted if the current version is at
   500  // the passed version.
   501  func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64,
   502  	q *WriteOptions, consulToken, vaultToken string) (*JobRegisterResponse, *WriteMeta, error) {
   503  
   504  	var resp JobRegisterResponse
   505  	req := &JobRevertRequest{
   506  		JobID:               jobID,
   507  		JobVersion:          version,
   508  		EnforcePriorVersion: enforcePriorVersion,
   509  		ConsulToken:         consulToken,
   510  		VaultToken:          vaultToken,
   511  	}
   512  	wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/revert", req, &resp, q)
   513  	if err != nil {
   514  		return nil, nil, err
   515  	}
   516  	return &resp, wm, nil
   517  }
   518  
   519  // Stable is used to mark a job version's stability.
   520  func (j *Jobs) Stable(jobID string, version uint64, stable bool,
   521  	q *WriteOptions) (*JobStabilityResponse, *WriteMeta, error) {
   522  
   523  	var resp JobStabilityResponse
   524  	req := &JobStabilityRequest{
   525  		JobID:      jobID,
   526  		JobVersion: version,
   527  		Stable:     stable,
   528  	}
   529  	wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/stable", req, &resp, q)
   530  	if err != nil {
   531  		return nil, nil, err
   532  	}
   533  	return &resp, wm, nil
   534  }
   535  
   536  // Services is used to return a list of service registrations associated to the
   537  // specified jobID.
   538  func (j *Jobs) Services(jobID string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) {
   539  	var resp []*ServiceRegistration
   540  	qm, err := j.client.query("/v1/job/"+jobID+"/services", &resp, q)
   541  	return resp, qm, err
   542  }
   543  
   544  // periodicForceResponse is used to deserialize a force response
   545  type periodicForceResponse struct {
   546  	EvalID string
   547  }
   548  
   549  // UpdateStrategy defines a task groups update strategy.
   550  type UpdateStrategy struct {
   551  	Stagger          *time.Duration `mapstructure:"stagger" hcl:"stagger,optional"`
   552  	MaxParallel      *int           `mapstructure:"max_parallel" hcl:"max_parallel,optional"`
   553  	HealthCheck      *string        `mapstructure:"health_check" hcl:"health_check,optional"`
   554  	MinHealthyTime   *time.Duration `mapstructure:"min_healthy_time" hcl:"min_healthy_time,optional"`
   555  	HealthyDeadline  *time.Duration `mapstructure:"healthy_deadline" hcl:"healthy_deadline,optional"`
   556  	ProgressDeadline *time.Duration `mapstructure:"progress_deadline" hcl:"progress_deadline,optional"`
   557  	Canary           *int           `mapstructure:"canary" hcl:"canary,optional"`
   558  	AutoRevert       *bool          `mapstructure:"auto_revert" hcl:"auto_revert,optional"`
   559  	AutoPromote      *bool          `mapstructure:"auto_promote" hcl:"auto_promote,optional"`
   560  }
   561  
   562  // DefaultUpdateStrategy provides a baseline that can be used to upgrade
   563  // jobs with the old policy or for populating field defaults.
   564  func DefaultUpdateStrategy() *UpdateStrategy {
   565  	return &UpdateStrategy{
   566  		Stagger:          pointerOf(30 * time.Second),
   567  		MaxParallel:      pointerOf(1),
   568  		HealthCheck:      pointerOf("checks"),
   569  		MinHealthyTime:   pointerOf(10 * time.Second),
   570  		HealthyDeadline:  pointerOf(5 * time.Minute),
   571  		ProgressDeadline: pointerOf(10 * time.Minute),
   572  		AutoRevert:       pointerOf(false),
   573  		Canary:           pointerOf(0),
   574  		AutoPromote:      pointerOf(false),
   575  	}
   576  }
   577  
   578  func (u *UpdateStrategy) Copy() *UpdateStrategy {
   579  	if u == nil {
   580  		return nil
   581  	}
   582  
   583  	copy := new(UpdateStrategy)
   584  
   585  	if u.Stagger != nil {
   586  		copy.Stagger = pointerOf(*u.Stagger)
   587  	}
   588  
   589  	if u.MaxParallel != nil {
   590  		copy.MaxParallel = pointerOf(*u.MaxParallel)
   591  	}
   592  
   593  	if u.HealthCheck != nil {
   594  		copy.HealthCheck = pointerOf(*u.HealthCheck)
   595  	}
   596  
   597  	if u.MinHealthyTime != nil {
   598  		copy.MinHealthyTime = pointerOf(*u.MinHealthyTime)
   599  	}
   600  
   601  	if u.HealthyDeadline != nil {
   602  		copy.HealthyDeadline = pointerOf(*u.HealthyDeadline)
   603  	}
   604  
   605  	if u.ProgressDeadline != nil {
   606  		copy.ProgressDeadline = pointerOf(*u.ProgressDeadline)
   607  	}
   608  
   609  	if u.AutoRevert != nil {
   610  		copy.AutoRevert = pointerOf(*u.AutoRevert)
   611  	}
   612  
   613  	if u.Canary != nil {
   614  		copy.Canary = pointerOf(*u.Canary)
   615  	}
   616  
   617  	if u.AutoPromote != nil {
   618  		copy.AutoPromote = pointerOf(*u.AutoPromote)
   619  	}
   620  
   621  	return copy
   622  }
   623  
   624  func (u *UpdateStrategy) Merge(o *UpdateStrategy) {
   625  	if o == nil {
   626  		return
   627  	}
   628  
   629  	if o.Stagger != nil {
   630  		u.Stagger = pointerOf(*o.Stagger)
   631  	}
   632  
   633  	if o.MaxParallel != nil {
   634  		u.MaxParallel = pointerOf(*o.MaxParallel)
   635  	}
   636  
   637  	if o.HealthCheck != nil {
   638  		u.HealthCheck = pointerOf(*o.HealthCheck)
   639  	}
   640  
   641  	if o.MinHealthyTime != nil {
   642  		u.MinHealthyTime = pointerOf(*o.MinHealthyTime)
   643  	}
   644  
   645  	if o.HealthyDeadline != nil {
   646  		u.HealthyDeadline = pointerOf(*o.HealthyDeadline)
   647  	}
   648  
   649  	if o.ProgressDeadline != nil {
   650  		u.ProgressDeadline = pointerOf(*o.ProgressDeadline)
   651  	}
   652  
   653  	if o.AutoRevert != nil {
   654  		u.AutoRevert = pointerOf(*o.AutoRevert)
   655  	}
   656  
   657  	if o.Canary != nil {
   658  		u.Canary = pointerOf(*o.Canary)
   659  	}
   660  
   661  	if o.AutoPromote != nil {
   662  		u.AutoPromote = pointerOf(*o.AutoPromote)
   663  	}
   664  }
   665  
   666  func (u *UpdateStrategy) Canonicalize() {
   667  	d := DefaultUpdateStrategy()
   668  
   669  	if u.MaxParallel == nil {
   670  		u.MaxParallel = d.MaxParallel
   671  	}
   672  
   673  	if u.Stagger == nil {
   674  		u.Stagger = d.Stagger
   675  	}
   676  
   677  	if u.HealthCheck == nil {
   678  		u.HealthCheck = d.HealthCheck
   679  	}
   680  
   681  	if u.HealthyDeadline == nil {
   682  		u.HealthyDeadline = d.HealthyDeadline
   683  	}
   684  
   685  	if u.ProgressDeadline == nil {
   686  		u.ProgressDeadline = d.ProgressDeadline
   687  	}
   688  
   689  	if u.MinHealthyTime == nil {
   690  		u.MinHealthyTime = d.MinHealthyTime
   691  	}
   692  
   693  	if u.AutoRevert == nil {
   694  		u.AutoRevert = d.AutoRevert
   695  	}
   696  
   697  	if u.Canary == nil {
   698  		u.Canary = d.Canary
   699  	}
   700  
   701  	if u.AutoPromote == nil {
   702  		u.AutoPromote = d.AutoPromote
   703  	}
   704  }
   705  
   706  // Empty returns whether the UpdateStrategy is empty or has user defined values.
   707  func (u *UpdateStrategy) Empty() bool {
   708  	if u == nil {
   709  		return true
   710  	}
   711  
   712  	if u.Stagger != nil && *u.Stagger != 0 {
   713  		return false
   714  	}
   715  
   716  	if u.MaxParallel != nil && *u.MaxParallel != 0 {
   717  		return false
   718  	}
   719  
   720  	if u.HealthCheck != nil && *u.HealthCheck != "" {
   721  		return false
   722  	}
   723  
   724  	if u.MinHealthyTime != nil && *u.MinHealthyTime != 0 {
   725  		return false
   726  	}
   727  
   728  	if u.HealthyDeadline != nil && *u.HealthyDeadline != 0 {
   729  		return false
   730  	}
   731  
   732  	if u.ProgressDeadline != nil && *u.ProgressDeadline != 0 {
   733  		return false
   734  	}
   735  
   736  	if u.AutoRevert != nil && *u.AutoRevert {
   737  		return false
   738  	}
   739  
   740  	if u.AutoPromote != nil && *u.AutoPromote {
   741  		return false
   742  	}
   743  
   744  	if u.Canary != nil && *u.Canary != 0 {
   745  		return false
   746  	}
   747  
   748  	return true
   749  }
   750  
   751  type Multiregion struct {
   752  	Strategy *MultiregionStrategy `hcl:"strategy,block"`
   753  	Regions  []*MultiregionRegion `hcl:"region,block"`
   754  }
   755  
   756  func (m *Multiregion) Canonicalize() {
   757  	if m.Strategy == nil {
   758  		m.Strategy = &MultiregionStrategy{
   759  			MaxParallel: pointerOf(0),
   760  			OnFailure:   pointerOf(""),
   761  		}
   762  	} else {
   763  		if m.Strategy.MaxParallel == nil {
   764  			m.Strategy.MaxParallel = pointerOf(0)
   765  		}
   766  		if m.Strategy.OnFailure == nil {
   767  			m.Strategy.OnFailure = pointerOf("")
   768  		}
   769  	}
   770  	if m.Regions == nil {
   771  		m.Regions = []*MultiregionRegion{}
   772  	}
   773  	for _, region := range m.Regions {
   774  		if region.Count == nil {
   775  			region.Count = pointerOf(1)
   776  		}
   777  		if region.Datacenters == nil {
   778  			region.Datacenters = []string{}
   779  		}
   780  		if region.Meta == nil {
   781  			region.Meta = map[string]string{}
   782  		}
   783  	}
   784  }
   785  
   786  func (m *Multiregion) Copy() *Multiregion {
   787  	if m == nil {
   788  		return nil
   789  	}
   790  	copy := new(Multiregion)
   791  	if m.Strategy != nil {
   792  		copy.Strategy = new(MultiregionStrategy)
   793  		copy.Strategy.MaxParallel = pointerOf(*m.Strategy.MaxParallel)
   794  		copy.Strategy.OnFailure = pointerOf(*m.Strategy.OnFailure)
   795  	}
   796  	for _, region := range m.Regions {
   797  		copyRegion := new(MultiregionRegion)
   798  		copyRegion.Name = region.Name
   799  		copyRegion.Count = pointerOf(*region.Count)
   800  		copyRegion.Datacenters = append(copyRegion.Datacenters, region.Datacenters...)
   801  		copyRegion.NodePool = region.NodePool
   802  		for k, v := range region.Meta {
   803  			copyRegion.Meta[k] = v
   804  		}
   805  
   806  		copy.Regions = append(copy.Regions, copyRegion)
   807  	}
   808  	return copy
   809  }
   810  
   811  type MultiregionStrategy struct {
   812  	MaxParallel *int    `mapstructure:"max_parallel" hcl:"max_parallel,optional"`
   813  	OnFailure   *string `mapstructure:"on_failure" hcl:"on_failure,optional"`
   814  }
   815  
   816  type MultiregionRegion struct {
   817  	Name        string            `hcl:",label"`
   818  	Count       *int              `hcl:"count,optional"`
   819  	Datacenters []string          `hcl:"datacenters,optional"`
   820  	NodePool    string            `hcl:"node_pool,optional"`
   821  	Meta        map[string]string `hcl:"meta,block"`
   822  }
   823  
   824  // PeriodicConfig is for serializing periodic config for a job.
   825  type PeriodicConfig struct {
   826  	Enabled         *bool    `hcl:"enabled,optional"`
   827  	Spec            *string  `hcl:"cron,optional"`
   828  	Specs           []string `hcl:"crons,optional"`
   829  	SpecType        *string
   830  	ProhibitOverlap *bool   `mapstructure:"prohibit_overlap" hcl:"prohibit_overlap,optional"`
   831  	TimeZone        *string `mapstructure:"time_zone" hcl:"time_zone,optional"`
   832  }
   833  
   834  func (p *PeriodicConfig) Canonicalize() {
   835  	if p.Enabled == nil {
   836  		p.Enabled = pointerOf(true)
   837  	}
   838  	if p.Spec == nil {
   839  		p.Spec = pointerOf("")
   840  	}
   841  	if p.Specs == nil {
   842  		p.Specs = []string{}
   843  	}
   844  	if p.SpecType == nil {
   845  		p.SpecType = pointerOf(PeriodicSpecCron)
   846  	}
   847  	if p.ProhibitOverlap == nil {
   848  		p.ProhibitOverlap = pointerOf(false)
   849  	}
   850  	if p.TimeZone == nil || *p.TimeZone == "" {
   851  		p.TimeZone = pointerOf("UTC")
   852  	}
   853  }
   854  
   855  // Next returns the closest time instant matching the spec that is after the
   856  // passed time. If no matching instance exists, the zero value of time.Time is
   857  // returned. The `time.Location` of the returned value matches that of the
   858  // passed time.
   859  func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) {
   860  	// Single spec parsing
   861  	if p != nil && *p.SpecType == PeriodicSpecCron {
   862  		if p.Spec != nil && *p.Spec != "" {
   863  			return cronParseNext(fromTime, *p.Spec)
   864  		}
   865  	}
   866  
   867  	// multiple specs parsing
   868  	var nextTime time.Time
   869  	for _, spec := range p.Specs {
   870  		t, err := cronParseNext(fromTime, spec)
   871  		if err != nil {
   872  			return time.Time{}, fmt.Errorf("failed parsing cron expression %s: %v", spec, err)
   873  		}
   874  		if nextTime.IsZero() || t.Before(nextTime) {
   875  			nextTime = t
   876  		}
   877  	}
   878  	return nextTime, nil
   879  }
   880  
   881  // cronParseNext is a helper that parses the next time for the given expression
   882  // but captures any panic that may occur in the underlying library.
   883  // ---  THIS FUNCTION IS REPLICATED IN nomad/structs/structs.go
   884  // and should be kept in sync.
   885  func cronParseNext(fromTime time.Time, spec string) (t time.Time, err error) {
   886  	defer func() {
   887  		if recover() != nil {
   888  			t = time.Time{}
   889  			err = fmt.Errorf("failed parsing cron expression: %q", spec)
   890  		}
   891  	}()
   892  	exp, err := cronexpr.Parse(spec)
   893  	if err != nil {
   894  		return time.Time{}, fmt.Errorf("failed parsing cron expression: %s: %v", spec, err)
   895  	}
   896  	return exp.Next(fromTime), nil
   897  }
   898  
   899  func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
   900  	if p.TimeZone == nil || *p.TimeZone == "" {
   901  		return time.UTC, nil
   902  	}
   903  
   904  	return time.LoadLocation(*p.TimeZone)
   905  }
   906  
   907  // ParameterizedJobConfig is used to configure the parameterized job.
   908  type ParameterizedJobConfig struct {
   909  	Payload      string   `hcl:"payload,optional"`
   910  	MetaRequired []string `mapstructure:"meta_required" hcl:"meta_required,optional"`
   911  	MetaOptional []string `mapstructure:"meta_optional" hcl:"meta_optional,optional"`
   912  }
   913  
   914  // JobSubmission is used to hold information about the original content of a job
   915  // specification being submitted to Nomad.
   916  //
   917  // At any time a JobSubmission may be nil, indicating no information is known about
   918  // the job submission.
   919  type JobSubmission struct {
   920  	// Source contains the original job definition (may be in the format of
   921  	// hcl1, hcl2, or json).
   922  	Source string
   923  
   924  	// Format indicates what the Source content was (hcl1, hcl2, or json).
   925  	Format string
   926  
   927  	// VariableFlags contains the CLI "-var" flag arguments as submitted with the
   928  	// job (hcl2 only).
   929  	VariableFlags map[string]string
   930  
   931  	// Variables contains the opaque variables configuration as coming from
   932  	// a var-file or the WebUI variables input (hcl2 only).
   933  	Variables string
   934  }
   935  
   936  func (js *JobSubmission) Canonicalize() {
   937  	if js == nil {
   938  		return
   939  	}
   940  
   941  	if len(js.VariableFlags) == 0 {
   942  		js.VariableFlags = nil
   943  	}
   944  }
   945  
   946  func (js *JobSubmission) Copy() *JobSubmission {
   947  	if js == nil {
   948  		return nil
   949  	}
   950  
   951  	return &JobSubmission{
   952  		Source:        js.Source,
   953  		Format:        js.Format,
   954  		VariableFlags: maps.Clone(js.VariableFlags),
   955  		Variables:     js.Variables,
   956  	}
   957  }
   958  
   959  // Job is used to serialize a job.
   960  type Job struct {
   961  	/* Fields parsed from HCL config */
   962  
   963  	Region           *string                 `hcl:"region,optional"`
   964  	Namespace        *string                 `hcl:"namespace,optional"`
   965  	ID               *string                 `hcl:"id,optional"`
   966  	Name             *string                 `hcl:"name,optional"`
   967  	Type             *string                 `hcl:"type,optional"`
   968  	Priority         *int                    `hcl:"priority,optional"`
   969  	AllAtOnce        *bool                   `mapstructure:"all_at_once" hcl:"all_at_once,optional"`
   970  	Datacenters      []string                `hcl:"datacenters,optional"`
   971  	NodePool         *string                 `mapstructure:"node_pool" hcl:"node_pool,optional"`
   972  	Constraints      []*Constraint           `hcl:"constraint,block"`
   973  	Affinities       []*Affinity             `hcl:"affinity,block"`
   974  	TaskGroups       []*TaskGroup            `hcl:"group,block"`
   975  	Update           *UpdateStrategy         `hcl:"update,block"`
   976  	Multiregion      *Multiregion            `hcl:"multiregion,block"`
   977  	Spreads          []*Spread               `hcl:"spread,block"`
   978  	Periodic         *PeriodicConfig         `hcl:"periodic,block"`
   979  	ParameterizedJob *ParameterizedJobConfig `hcl:"parameterized,block"`
   980  	Reschedule       *ReschedulePolicy       `hcl:"reschedule,block"`
   981  	Migrate          *MigrateStrategy        `hcl:"migrate,block"`
   982  	Meta             map[string]string       `hcl:"meta,block"`
   983  	ConsulToken      *string                 `mapstructure:"consul_token" hcl:"consul_token,optional"`
   984  	VaultToken       *string                 `mapstructure:"vault_token" hcl:"vault_token,optional"`
   985  
   986  	/* Fields set by server, not sourced from job config file */
   987  
   988  	Stop                     *bool
   989  	ParentID                 *string
   990  	Dispatched               bool
   991  	DispatchIdempotencyToken *string
   992  	Payload                  []byte
   993  	ConsulNamespace          *string `mapstructure:"consul_namespace"`
   994  	VaultNamespace           *string `mapstructure:"vault_namespace"`
   995  	NomadTokenID             *string `mapstructure:"nomad_token_id"`
   996  	Status                   *string
   997  	StatusDescription        *string
   998  	Stable                   *bool
   999  	Version                  *uint64
  1000  	SubmitTime               *int64
  1001  	CreateIndex              *uint64
  1002  	ModifyIndex              *uint64
  1003  	JobModifyIndex           *uint64
  1004  }
  1005  
  1006  // IsPeriodic returns whether a job is periodic.
  1007  func (j *Job) IsPeriodic() bool {
  1008  	return j.Periodic != nil
  1009  }
  1010  
  1011  // IsParameterized returns whether a job is parameterized job.
  1012  func (j *Job) IsParameterized() bool {
  1013  	return j.ParameterizedJob != nil && !j.Dispatched
  1014  }
  1015  
  1016  // IsMultiregion returns whether a job is a multiregion job
  1017  func (j *Job) IsMultiregion() bool {
  1018  	return j.Multiregion != nil && j.Multiregion.Regions != nil && len(j.Multiregion.Regions) > 0
  1019  }
  1020  
  1021  func (j *Job) Canonicalize() {
  1022  	if j.ID == nil {
  1023  		j.ID = pointerOf("")
  1024  	}
  1025  	if j.Name == nil {
  1026  		j.Name = pointerOf(*j.ID)
  1027  	}
  1028  	if j.ParentID == nil {
  1029  		j.ParentID = pointerOf("")
  1030  	}
  1031  	if j.Namespace == nil {
  1032  		j.Namespace = pointerOf(DefaultNamespace)
  1033  	}
  1034  	if j.Priority == nil {
  1035  		j.Priority = pointerOf(JobDefaultPriority)
  1036  	}
  1037  	if j.Stop == nil {
  1038  		j.Stop = pointerOf(false)
  1039  	}
  1040  	if j.Region == nil {
  1041  		j.Region = pointerOf(GlobalRegion)
  1042  	}
  1043  	if j.NodePool == nil {
  1044  		j.NodePool = pointerOf("")
  1045  	}
  1046  	if j.Type == nil {
  1047  		j.Type = pointerOf("service")
  1048  	}
  1049  	if j.AllAtOnce == nil {
  1050  		j.AllAtOnce = pointerOf(false)
  1051  	}
  1052  	if j.ConsulToken == nil {
  1053  		j.ConsulToken = pointerOf("")
  1054  	}
  1055  	if j.ConsulNamespace == nil {
  1056  		j.ConsulNamespace = pointerOf("")
  1057  	}
  1058  	if j.VaultToken == nil {
  1059  		j.VaultToken = pointerOf("")
  1060  	}
  1061  	if j.VaultNamespace == nil {
  1062  		j.VaultNamespace = pointerOf("")
  1063  	}
  1064  	if j.NomadTokenID == nil {
  1065  		j.NomadTokenID = pointerOf("")
  1066  	}
  1067  	if j.Status == nil {
  1068  		j.Status = pointerOf("")
  1069  	}
  1070  	if j.StatusDescription == nil {
  1071  		j.StatusDescription = pointerOf("")
  1072  	}
  1073  	if j.Stable == nil {
  1074  		j.Stable = pointerOf(false)
  1075  	}
  1076  	if j.Version == nil {
  1077  		j.Version = pointerOf(uint64(0))
  1078  	}
  1079  	if j.CreateIndex == nil {
  1080  		j.CreateIndex = pointerOf(uint64(0))
  1081  	}
  1082  	if j.ModifyIndex == nil {
  1083  		j.ModifyIndex = pointerOf(uint64(0))
  1084  	}
  1085  	if j.JobModifyIndex == nil {
  1086  		j.JobModifyIndex = pointerOf(uint64(0))
  1087  	}
  1088  	if j.Periodic != nil {
  1089  		j.Periodic.Canonicalize()
  1090  	}
  1091  	if j.Update != nil {
  1092  		j.Update.Canonicalize()
  1093  	} else if *j.Type == JobTypeService {
  1094  		j.Update = DefaultUpdateStrategy()
  1095  	}
  1096  	if j.Multiregion != nil {
  1097  		j.Multiregion.Canonicalize()
  1098  	}
  1099  
  1100  	for _, tg := range j.TaskGroups {
  1101  		tg.Canonicalize(j)
  1102  	}
  1103  
  1104  	for _, spread := range j.Spreads {
  1105  		spread.Canonicalize()
  1106  	}
  1107  	for _, a := range j.Affinities {
  1108  		a.Canonicalize()
  1109  	}
  1110  }
  1111  
  1112  // LookupTaskGroup finds a task group by name
  1113  func (j *Job) LookupTaskGroup(name string) *TaskGroup {
  1114  	for _, tg := range j.TaskGroups {
  1115  		if *tg.Name == name {
  1116  			return tg
  1117  		}
  1118  	}
  1119  	return nil
  1120  }
  1121  
  1122  // JobSummary summarizes the state of the allocations of a job
  1123  type JobSummary struct {
  1124  	JobID     string
  1125  	Namespace string
  1126  	Summary   map[string]TaskGroupSummary
  1127  	Children  *JobChildrenSummary
  1128  
  1129  	// Raft Indexes
  1130  	CreateIndex uint64
  1131  	ModifyIndex uint64
  1132  }
  1133  
  1134  // JobChildrenSummary contains the summary of children job status
  1135  type JobChildrenSummary struct {
  1136  	Pending int64
  1137  	Running int64
  1138  	Dead    int64
  1139  }
  1140  
  1141  func (jc *JobChildrenSummary) Sum() int {
  1142  	if jc == nil {
  1143  		return 0
  1144  	}
  1145  
  1146  	return int(jc.Pending + jc.Running + jc.Dead)
  1147  }
  1148  
  1149  // TaskGroup summarizes the state of all the allocations of a particular
  1150  // TaskGroup
  1151  type TaskGroupSummary struct {
  1152  	Queued   int
  1153  	Complete int
  1154  	Failed   int
  1155  	Running  int
  1156  	Starting int
  1157  	Lost     int
  1158  	Unknown  int
  1159  }
  1160  
  1161  // JobListStub is used to return a subset of information about
  1162  // jobs during list operations.
  1163  type JobListStub struct {
  1164  	ID                string
  1165  	ParentID          string
  1166  	Name              string
  1167  	Namespace         string `json:",omitempty"`
  1168  	Datacenters       []string
  1169  	Type              string
  1170  	Priority          int
  1171  	Periodic          bool
  1172  	ParameterizedJob  bool
  1173  	Stop              bool
  1174  	Status            string
  1175  	StatusDescription string
  1176  	JobSummary        *JobSummary
  1177  	CreateIndex       uint64
  1178  	ModifyIndex       uint64
  1179  	JobModifyIndex    uint64
  1180  	SubmitTime        int64
  1181  	Meta              map[string]string `json:",omitempty"`
  1182  }
  1183  
  1184  // JobIDSort is used to sort jobs by their job ID's.
  1185  type JobIDSort []*JobListStub
  1186  
  1187  func (j JobIDSort) Len() int {
  1188  	return len(j)
  1189  }
  1190  
  1191  func (j JobIDSort) Less(a, b int) bool {
  1192  	return j[a].ID < j[b].ID
  1193  }
  1194  
  1195  func (j JobIDSort) Swap(a, b int) {
  1196  	j[a], j[b] = j[b], j[a]
  1197  }
  1198  
  1199  // NewServiceJob creates and returns a new service-style job
  1200  // for long-lived processes using the provided name, ID, and
  1201  // relative job priority.
  1202  func NewServiceJob(id, name, region string, pri int) *Job {
  1203  	return newJob(id, name, region, JobTypeService, pri)
  1204  }
  1205  
  1206  // NewBatchJob creates and returns a new batch-style job for
  1207  // short-lived processes using the provided name and ID along
  1208  // with the relative job priority.
  1209  func NewBatchJob(id, name, region string, pri int) *Job {
  1210  	return newJob(id, name, region, JobTypeBatch, pri)
  1211  }
  1212  
  1213  // NewSystemJob creates and returns a new system-style job for processes
  1214  // designed to run on all clients, using the provided name and ID along with
  1215  // the relative job priority.
  1216  func NewSystemJob(id, name, region string, pri int) *Job {
  1217  	return newJob(id, name, region, JobTypeSystem, pri)
  1218  }
  1219  
  1220  // NewSysbatchJob creates and returns a new sysbatch-style job for short-lived
  1221  // processes designed to run on all clients, using the provided name and ID
  1222  // along with the relative job priority.
  1223  func NewSysbatchJob(id, name, region string, pri int) *Job {
  1224  	return newJob(id, name, region, JobTypeSysbatch, pri)
  1225  }
  1226  
  1227  // newJob is used to create a new Job struct.
  1228  func newJob(id, name, region, typ string, pri int) *Job {
  1229  	return &Job{
  1230  		Region:   &region,
  1231  		ID:       &id,
  1232  		Name:     &name,
  1233  		Type:     &typ,
  1234  		Priority: &pri,
  1235  	}
  1236  }
  1237  
  1238  // SetMeta is used to set arbitrary k/v pairs of metadata on a job.
  1239  func (j *Job) SetMeta(key, val string) *Job {
  1240  	if j.Meta == nil {
  1241  		j.Meta = make(map[string]string)
  1242  	}
  1243  	j.Meta[key] = val
  1244  	return j
  1245  }
  1246  
  1247  // AddDatacenter is used to add a datacenter to a job.
  1248  func (j *Job) AddDatacenter(dc string) *Job {
  1249  	j.Datacenters = append(j.Datacenters, dc)
  1250  	return j
  1251  }
  1252  
  1253  // Constrain is used to add a constraint to a job.
  1254  func (j *Job) Constrain(c *Constraint) *Job {
  1255  	j.Constraints = append(j.Constraints, c)
  1256  	return j
  1257  }
  1258  
  1259  // AddAffinity is used to add an affinity to a job.
  1260  func (j *Job) AddAffinity(a *Affinity) *Job {
  1261  	j.Affinities = append(j.Affinities, a)
  1262  	return j
  1263  }
  1264  
  1265  // AddTaskGroup adds a task group to an existing job.
  1266  func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
  1267  	j.TaskGroups = append(j.TaskGroups, grp)
  1268  	return j
  1269  }
  1270  
  1271  // AddPeriodicConfig adds a periodic config to an existing job.
  1272  func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job {
  1273  	j.Periodic = cfg
  1274  	return j
  1275  }
  1276  
  1277  func (j *Job) AddSpread(s *Spread) *Job {
  1278  	j.Spreads = append(j.Spreads, s)
  1279  	return j
  1280  }
  1281  
  1282  type WriteRequest struct {
  1283  	// The target region for this write
  1284  	Region string
  1285  
  1286  	// Namespace is the target namespace for this write
  1287  	Namespace string
  1288  
  1289  	// SecretID is the secret ID of an ACL token
  1290  	SecretID string
  1291  }
  1292  
  1293  // JobValidateRequest is used to validate a job
  1294  type JobValidateRequest struct {
  1295  	Job *Job
  1296  	WriteRequest
  1297  }
  1298  
  1299  // JobValidateResponse is the response from validate request
  1300  type JobValidateResponse struct {
  1301  	// DriverConfigValidated indicates whether the agent validated the driver
  1302  	// config
  1303  	DriverConfigValidated bool
  1304  
  1305  	// ValidationErrors is a list of validation errors
  1306  	ValidationErrors []string
  1307  
  1308  	// Error is a string version of any error that may have occurred
  1309  	Error string
  1310  
  1311  	// Warnings contains any warnings about the given job. These may include
  1312  	// deprecation warnings.
  1313  	Warnings string
  1314  }
  1315  
  1316  // JobRevertRequest is used to revert a job to a prior version.
  1317  type JobRevertRequest struct {
  1318  	// JobID is the ID of the job  being reverted
  1319  	JobID string
  1320  
  1321  	// JobVersion the version to revert to.
  1322  	JobVersion uint64
  1323  
  1324  	// EnforcePriorVersion if set will enforce that the job is at the given
  1325  	// version before reverting.
  1326  	EnforcePriorVersion *uint64
  1327  
  1328  	// ConsulToken is the Consul token that proves the submitter of the job revert
  1329  	// has access to the Service Identity policies associated with the job's
  1330  	// Consul Connect enabled services. This field is only used to transfer the
  1331  	// token and is not stored after the Job revert.
  1332  	ConsulToken string `json:",omitempty"`
  1333  
  1334  	// VaultToken is the Vault token that proves the submitter of the job revert
  1335  	// has access to any Vault policies specified in the targeted job version. This
  1336  	// field is only used to authorize the revert and is not stored after the Job
  1337  	// revert.
  1338  	VaultToken string `json:",omitempty"`
  1339  
  1340  	WriteRequest
  1341  }
  1342  
  1343  // JobRegisterRequest is used to update a job
  1344  type JobRegisterRequest struct {
  1345  	Submission *JobSubmission
  1346  	Job        *Job
  1347  
  1348  	// If EnforceIndex is set then the job will only be registered if the passed
  1349  	// JobModifyIndex matches the current Jobs index. If the index is zero, the
  1350  	// register only occurs if the job is new.
  1351  	EnforceIndex   bool   `json:",omitempty"`
  1352  	JobModifyIndex uint64 `json:",omitempty"`
  1353  	PolicyOverride bool   `json:",omitempty"`
  1354  	PreserveCounts bool   `json:",omitempty"`
  1355  
  1356  	// EvalPriority is an optional priority to use on any evaluation created as
  1357  	// a result on this job registration. This value must be between 1-100
  1358  	// inclusively, where a larger value corresponds to a higher priority. This
  1359  	// is useful when an operator wishes to push through a job registration in
  1360  	// busy clusters with a large evaluation backlog. This avoids needing to
  1361  	// change the job priority which also impacts preemption.
  1362  	EvalPriority int `json:",omitempty"`
  1363  
  1364  	WriteRequest
  1365  }
  1366  
  1367  // JobRegisterResponse is used to respond to a job registration
  1368  type JobRegisterResponse struct {
  1369  	EvalID          string
  1370  	EvalCreateIndex uint64
  1371  	JobModifyIndex  uint64
  1372  
  1373  	// Warnings contains any warnings about the given job. These may include
  1374  	// deprecation warnings.
  1375  	Warnings string
  1376  
  1377  	QueryMeta
  1378  }
  1379  
  1380  // JobDeregisterResponse is used to respond to a job deregistration
  1381  type JobDeregisterResponse struct {
  1382  	EvalID          string
  1383  	EvalCreateIndex uint64
  1384  	JobModifyIndex  uint64
  1385  	QueryMeta
  1386  }
  1387  
  1388  type JobPlanRequest struct {
  1389  	Job            *Job
  1390  	Diff           bool
  1391  	PolicyOverride bool
  1392  	WriteRequest
  1393  }
  1394  
  1395  type JobPlanResponse struct {
  1396  	JobModifyIndex     uint64
  1397  	CreatedEvals       []*Evaluation
  1398  	Diff               *JobDiff
  1399  	Annotations        *PlanAnnotations
  1400  	FailedTGAllocs     map[string]*AllocationMetric
  1401  	NextPeriodicLaunch time.Time
  1402  
  1403  	// Warnings contains any warnings about the given job. These may include
  1404  	// deprecation warnings.
  1405  	Warnings string
  1406  }
  1407  
  1408  type JobDiff struct {
  1409  	Type       string
  1410  	ID         string
  1411  	Fields     []*FieldDiff
  1412  	Objects    []*ObjectDiff
  1413  	TaskGroups []*TaskGroupDiff
  1414  }
  1415  
  1416  type TaskGroupDiff struct {
  1417  	Type    string
  1418  	Name    string
  1419  	Fields  []*FieldDiff
  1420  	Objects []*ObjectDiff
  1421  	Tasks   []*TaskDiff
  1422  	Updates map[string]uint64
  1423  }
  1424  
  1425  type TaskDiff struct {
  1426  	Type        string
  1427  	Name        string
  1428  	Fields      []*FieldDiff
  1429  	Objects     []*ObjectDiff
  1430  	Annotations []string
  1431  }
  1432  
  1433  type FieldDiff struct {
  1434  	Type        string
  1435  	Name        string
  1436  	Old, New    string
  1437  	Annotations []string
  1438  }
  1439  
  1440  type ObjectDiff struct {
  1441  	Type    string
  1442  	Name    string
  1443  	Fields  []*FieldDiff
  1444  	Objects []*ObjectDiff
  1445  }
  1446  
  1447  type PlanAnnotations struct {
  1448  	DesiredTGUpdates map[string]*DesiredUpdates
  1449  	PreemptedAllocs  []*AllocationListStub
  1450  }
  1451  
  1452  type DesiredUpdates struct {
  1453  	Ignore            uint64
  1454  	Place             uint64
  1455  	Migrate           uint64
  1456  	Stop              uint64
  1457  	InPlaceUpdate     uint64
  1458  	DestructiveUpdate uint64
  1459  	Canary            uint64
  1460  	Preemptions       uint64
  1461  }
  1462  
  1463  type JobDispatchRequest struct {
  1464  	JobID            string
  1465  	Payload          []byte
  1466  	Meta             map[string]string
  1467  	IdPrefixTemplate string
  1468  }
  1469  
  1470  type JobDispatchResponse struct {
  1471  	DispatchedJobID string
  1472  	EvalID          string
  1473  	EvalCreateIndex uint64
  1474  	JobCreateIndex  uint64
  1475  	WriteMeta
  1476  }
  1477  
  1478  // JobVersionsResponse is used for a job get versions request
  1479  type JobVersionsResponse struct {
  1480  	Versions []*Job
  1481  	Diffs    []*JobDiff
  1482  	QueryMeta
  1483  }
  1484  
  1485  // JobSubmissionResponse is used for a job get submission request
  1486  type JobSubmissionResponse struct {
  1487  	Submission *JobSubmission
  1488  	QueryMeta
  1489  }
  1490  
  1491  // JobStabilityRequest is used to marked a job as stable.
  1492  type JobStabilityRequest struct {
  1493  	// Job to set the stability on
  1494  	JobID      string
  1495  	JobVersion uint64
  1496  
  1497  	// Set the stability
  1498  	Stable bool
  1499  	WriteRequest
  1500  }
  1501  
  1502  // JobStabilityResponse is the response when marking a job as stable.
  1503  type JobStabilityResponse struct {
  1504  	JobModifyIndex uint64
  1505  	WriteMeta
  1506  }
  1507  
  1508  // JobEvaluateRequest is used when we just need to re-evaluate a target job
  1509  type JobEvaluateRequest struct {
  1510  	JobID       string
  1511  	EvalOptions EvalOptions
  1512  	WriteRequest
  1513  }
  1514  
  1515  // EvalOptions is used to encapsulate options when forcing a job evaluation
  1516  type EvalOptions struct {
  1517  	ForceReschedule bool
  1518  }
  1519  
  1520  // ActionExec is used to run a pre-defined command inside a running task.
  1521  // The call blocks until command terminates (or an error occurs), and returns the exit code.
  1522  func (j *Jobs) ActionExec(ctx context.Context,
  1523  	alloc *Allocation, job string, task string, tty bool, command []string,
  1524  	action string,
  1525  	stdin io.Reader, stdout, stderr io.Writer,
  1526  	terminalSizeCh <-chan TerminalSize, q *QueryOptions) (exitCode int, err error) {
  1527  
  1528  	s := &execSession{
  1529  		client:  j.client,
  1530  		alloc:   alloc,
  1531  		job:     job,
  1532  		task:    task,
  1533  		tty:     tty,
  1534  		command: command,
  1535  		action:  action,
  1536  
  1537  		stdin:  stdin,
  1538  		stdout: stdout,
  1539  		stderr: stderr,
  1540  
  1541  		terminalSizeCh: terminalSizeCh,
  1542  		q:              q,
  1543  	}
  1544  
  1545  	return s.run(ctx)
  1546  }