github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/api/jobs.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"sort"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/gorhill/cronexpr"
    11  	"github.com/hashicorp/nomad/helper"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  )
    14  
    15  const (
    16  	// JobTypeService indicates a long-running processes
    17  	JobTypeService = "service"
    18  
    19  	// JobTypeBatch indicates a short-lived process
    20  	JobTypeBatch = "batch"
    21  
    22  	// PeriodicSpecCron is used for a cron spec.
    23  	PeriodicSpecCron = "cron"
    24  
    25  	// DefaultNamespace is the default namespace.
    26  	DefaultNamespace = "default"
    27  )
    28  
    29  const (
    30  	// RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by
    31  	// enforcing the job modify index during registers.
    32  	RegisterEnforceIndexErrPrefix = "Enforcing job modify index"
    33  )
    34  
    35  // Jobs is used to access the job-specific endpoints.
    36  type Jobs struct {
    37  	client *Client
    38  }
    39  
    40  // JobsParseRequest is used for arguments of the /vi/jobs/parse endpoint
    41  type JobsParseRequest struct {
    42  	// JobHCL is an hcl jobspec
    43  	JobHCL string
    44  
    45  	// Canonicalize is a flag as to if the server should return default values
    46  	// for unset fields
    47  	Canonicalize bool
    48  }
    49  
    50  // Jobs returns a handle on the jobs endpoints.
    51  func (c *Client) Jobs() *Jobs {
    52  	return &Jobs{client: c}
    53  }
    54  
    55  // Parse is used to convert the HCL repesentation of a Job to JSON server side.
    56  // To parse the HCL client side see package github.com/hashicorp/nomad/jobspec
    57  func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) {
    58  	var job Job
    59  	req := &JobsParseRequest{
    60  		JobHCL:       jobHCL,
    61  		Canonicalize: canonicalize,
    62  	}
    63  	_, err := j.client.write("/v1/jobs/parse", req, &job, nil)
    64  	return &job, err
    65  }
    66  
    67  func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) {
    68  	var resp JobValidateResponse
    69  	req := &JobValidateRequest{Job: job}
    70  	if q != nil {
    71  		req.WriteRequest = WriteRequest{Region: q.Region}
    72  	}
    73  	wm, err := j.client.write("/v1/validate/job", req, &resp, q)
    74  	return &resp, wm, err
    75  }
    76  
    77  // RegisterOptions is used to pass through job registration parameters
    78  type RegisterOptions struct {
    79  	EnforceIndex   bool
    80  	ModifyIndex    uint64
    81  	PolicyOverride bool
    82  }
    83  
    84  // Register is used to register a new job. It returns the ID
    85  // of the evaluation, along with any errors encountered.
    86  func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
    87  	return j.RegisterOpts(job, nil, q)
    88  }
    89  
    90  // EnforceRegister is used to register a job enforcing its job modify index.
    91  func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
    92  	opts := RegisterOptions{EnforceIndex: true, ModifyIndex: modifyIndex}
    93  	return j.RegisterOpts(job, &opts, q)
    94  }
    95  
    96  // Register is used to register a new job. It returns the ID
    97  // of the evaluation, along with any errors encountered.
    98  func (j *Jobs) RegisterOpts(job *Job, opts *RegisterOptions, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
    99  	// Format the request
   100  	req := &RegisterJobRequest{
   101  		Job: job,
   102  	}
   103  	if opts != nil {
   104  		if opts.EnforceIndex {
   105  			req.EnforceIndex = true
   106  			req.JobModifyIndex = opts.ModifyIndex
   107  		}
   108  		if opts.PolicyOverride {
   109  			req.PolicyOverride = true
   110  		}
   111  	}
   112  
   113  	var resp JobRegisterResponse
   114  	wm, err := j.client.write("/v1/jobs", req, &resp, q)
   115  	if err != nil {
   116  		return nil, nil, err
   117  	}
   118  	return &resp, wm, nil
   119  }
   120  
   121  // List is used to list all of the existing jobs.
   122  func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) {
   123  	var resp []*JobListStub
   124  	qm, err := j.client.query("/v1/jobs", &resp, q)
   125  	if err != nil {
   126  		return nil, qm, err
   127  	}
   128  	sort.Sort(JobIDSort(resp))
   129  	return resp, qm, nil
   130  }
   131  
   132  // PrefixList is used to list all existing jobs that match the prefix.
   133  func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) {
   134  	return j.List(&QueryOptions{Prefix: prefix})
   135  }
   136  
   137  // Info is used to retrieve information about a particular
   138  // job given its unique ID.
   139  func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
   140  	var resp Job
   141  	qm, err := j.client.query("/v1/job/"+jobID, &resp, q)
   142  	if err != nil {
   143  		return nil, nil, err
   144  	}
   145  	return &resp, qm, nil
   146  }
   147  
   148  // Versions is used to retrieve all versions of a particular job given its
   149  // unique ID.
   150  func (j *Jobs) Versions(jobID string, diffs bool, q *QueryOptions) ([]*Job, []*JobDiff, *QueryMeta, error) {
   151  	var resp JobVersionsResponse
   152  	qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/versions?diffs=%v", jobID, diffs), &resp, q)
   153  	if err != nil {
   154  		return nil, nil, nil, err
   155  	}
   156  	return resp.Versions, resp.Diffs, qm, nil
   157  }
   158  
   159  // Allocations is used to return the allocs for a given job ID.
   160  func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
   161  	var resp []*AllocationListStub
   162  	u, err := url.Parse("/v1/job/" + jobID + "/allocations")
   163  	if err != nil {
   164  		return nil, nil, err
   165  	}
   166  
   167  	v := u.Query()
   168  	v.Add("all", strconv.FormatBool(allAllocs))
   169  	u.RawQuery = v.Encode()
   170  
   171  	qm, err := j.client.query(u.String(), &resp, q)
   172  	if err != nil {
   173  		return nil, nil, err
   174  	}
   175  	sort.Sort(AllocIndexSort(resp))
   176  	return resp, qm, nil
   177  }
   178  
   179  // Deployments is used to query the deployments associated with the given job
   180  // ID.
   181  func (j *Jobs) Deployments(jobID string, q *QueryOptions) ([]*Deployment, *QueryMeta, error) {
   182  	var resp []*Deployment
   183  	qm, err := j.client.query("/v1/job/"+jobID+"/deployments", &resp, q)
   184  	if err != nil {
   185  		return nil, nil, err
   186  	}
   187  	sort.Sort(DeploymentIndexSort(resp))
   188  	return resp, qm, nil
   189  }
   190  
   191  // LatestDeployment is used to query for the latest deployment associated with
   192  // the given job ID.
   193  func (j *Jobs) LatestDeployment(jobID string, q *QueryOptions) (*Deployment, *QueryMeta, error) {
   194  	var resp *Deployment
   195  	qm, err := j.client.query("/v1/job/"+jobID+"/deployment", &resp, q)
   196  	if err != nil {
   197  		return nil, nil, err
   198  	}
   199  	return resp, qm, nil
   200  }
   201  
   202  // Evaluations is used to query the evaluations associated with the given job
   203  // ID.
   204  func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) {
   205  	var resp []*Evaluation
   206  	qm, err := j.client.query("/v1/job/"+jobID+"/evaluations", &resp, q)
   207  	if err != nil {
   208  		return nil, nil, err
   209  	}
   210  	sort.Sort(EvalIndexSort(resp))
   211  	return resp, qm, nil
   212  }
   213  
   214  // Deregister is used to remove an existing job. If purge is set to true, the job
   215  // is deregistered and purged from the system versus still being queryable and
   216  // eventually GC'ed from the system. Most callers should not specify purge.
   217  func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) {
   218  	var resp JobDeregisterResponse
   219  	wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", jobID, purge), &resp, q)
   220  	if err != nil {
   221  		return "", nil, err
   222  	}
   223  	return resp.EvalID, wm, nil
   224  }
   225  
   226  // ForceEvaluate is used to force-evaluate an existing job.
   227  func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   228  	var resp JobRegisterResponse
   229  	wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", nil, &resp, q)
   230  	if err != nil {
   231  		return "", nil, err
   232  	}
   233  	return resp.EvalID, wm, nil
   234  }
   235  
   236  // EvaluateWithOpts is used to force-evaluate an existing job and takes additional options
   237  // for whether to force reschedule failed allocations
   238  func (j *Jobs) EvaluateWithOpts(jobID string, opts EvalOptions, q *WriteOptions) (string, *WriteMeta, error) {
   239  	req := &JobEvaluateRequest{
   240  		JobID:       jobID,
   241  		EvalOptions: opts,
   242  	}
   243  
   244  	var resp JobRegisterResponse
   245  	wm, err := j.client.write("/v1/job/"+jobID+"/evaluate", req, &resp, q)
   246  	if err != nil {
   247  		return "", nil, err
   248  	}
   249  	return resp.EvalID, wm, nil
   250  }
   251  
   252  // PeriodicForce spawns a new instance of the periodic job and returns the eval ID
   253  func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) {
   254  	var resp periodicForceResponse
   255  	wm, err := j.client.write("/v1/job/"+jobID+"/periodic/force", nil, &resp, q)
   256  	if err != nil {
   257  		return "", nil, err
   258  	}
   259  	return resp.EvalID, wm, nil
   260  }
   261  
   262  // PlanOptions is used to pass through job planning parameters
   263  type PlanOptions struct {
   264  	Diff           bool
   265  	PolicyOverride bool
   266  }
   267  
   268  func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   269  	opts := PlanOptions{Diff: diff}
   270  	return j.PlanOpts(job, &opts, q)
   271  }
   272  
   273  func (j *Jobs) PlanOpts(job *Job, opts *PlanOptions, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) {
   274  	if job == nil {
   275  		return nil, nil, fmt.Errorf("must pass non-nil job")
   276  	}
   277  
   278  	// Setup the request
   279  	req := &JobPlanRequest{
   280  		Job: job,
   281  	}
   282  	if opts != nil {
   283  		req.Diff = opts.Diff
   284  		req.PolicyOverride = opts.PolicyOverride
   285  	}
   286  
   287  	var resp JobPlanResponse
   288  	wm, err := j.client.write("/v1/job/"+*job.ID+"/plan", req, &resp, q)
   289  	if err != nil {
   290  		return nil, nil, err
   291  	}
   292  	return &resp, wm, nil
   293  }
   294  
   295  func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) {
   296  	var resp JobSummary
   297  	qm, err := j.client.query("/v1/job/"+jobID+"/summary", &resp, q)
   298  	if err != nil {
   299  		return nil, nil, err
   300  	}
   301  	return &resp, qm, nil
   302  }
   303  
   304  func (j *Jobs) Dispatch(jobID string, meta map[string]string,
   305  	payload []byte, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) {
   306  	var resp JobDispatchResponse
   307  	req := &JobDispatchRequest{
   308  		JobID:   jobID,
   309  		Meta:    meta,
   310  		Payload: payload,
   311  	}
   312  	wm, err := j.client.write("/v1/job/"+jobID+"/dispatch", req, &resp, q)
   313  	if err != nil {
   314  		return nil, nil, err
   315  	}
   316  	return &resp, wm, nil
   317  }
   318  
   319  // Revert is used to revert the given job to the passed version. If
   320  // enforceVersion is set, the job is only reverted if the current version is at
   321  // the passed version.
   322  func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64,
   323  	q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) {
   324  
   325  	var resp JobRegisterResponse
   326  	req := &JobRevertRequest{
   327  		JobID:               jobID,
   328  		JobVersion:          version,
   329  		EnforcePriorVersion: enforcePriorVersion,
   330  	}
   331  	wm, err := j.client.write("/v1/job/"+jobID+"/revert", req, &resp, q)
   332  	if err != nil {
   333  		return nil, nil, err
   334  	}
   335  	return &resp, wm, nil
   336  }
   337  
   338  // Stable is used to mark a job version's stability.
   339  func (j *Jobs) Stable(jobID string, version uint64, stable bool,
   340  	q *WriteOptions) (*JobStabilityResponse, *WriteMeta, error) {
   341  
   342  	var resp JobStabilityResponse
   343  	req := &JobStabilityRequest{
   344  		JobID:      jobID,
   345  		JobVersion: version,
   346  		Stable:     stable,
   347  	}
   348  	wm, err := j.client.write("/v1/job/"+jobID+"/stable", req, &resp, q)
   349  	if err != nil {
   350  		return nil, nil, err
   351  	}
   352  	return &resp, wm, nil
   353  }
   354  
   355  // periodicForceResponse is used to deserialize a force response
   356  type periodicForceResponse struct {
   357  	EvalID string
   358  }
   359  
   360  // UpdateStrategy defines a task groups update strategy.
   361  type UpdateStrategy struct {
   362  	Stagger          *time.Duration `mapstructure:"stagger"`
   363  	MaxParallel      *int           `mapstructure:"max_parallel"`
   364  	HealthCheck      *string        `mapstructure:"health_check"`
   365  	MinHealthyTime   *time.Duration `mapstructure:"min_healthy_time"`
   366  	HealthyDeadline  *time.Duration `mapstructure:"healthy_deadline"`
   367  	ProgressDeadline *time.Duration `mapstructure:"progress_deadline"`
   368  	AutoRevert       *bool          `mapstructure:"auto_revert"`
   369  	Canary           *int           `mapstructure:"canary"`
   370  }
   371  
   372  // DefaultUpdateStrategy provides a baseline that can be used to upgrade
   373  // jobs with the old policy or for populating field defaults.
   374  func DefaultUpdateStrategy() *UpdateStrategy {
   375  	return &UpdateStrategy{
   376  		Stagger:          helper.TimeToPtr(30 * time.Second),
   377  		MaxParallel:      helper.IntToPtr(1),
   378  		HealthCheck:      helper.StringToPtr("checks"),
   379  		MinHealthyTime:   helper.TimeToPtr(10 * time.Second),
   380  		HealthyDeadline:  helper.TimeToPtr(5 * time.Minute),
   381  		ProgressDeadline: helper.TimeToPtr(10 * time.Minute),
   382  		AutoRevert:       helper.BoolToPtr(false),
   383  		Canary:           helper.IntToPtr(0),
   384  	}
   385  }
   386  
   387  func (u *UpdateStrategy) Copy() *UpdateStrategy {
   388  	if u == nil {
   389  		return nil
   390  	}
   391  
   392  	copy := new(UpdateStrategy)
   393  
   394  	if u.Stagger != nil {
   395  		copy.Stagger = helper.TimeToPtr(*u.Stagger)
   396  	}
   397  
   398  	if u.MaxParallel != nil {
   399  		copy.MaxParallel = helper.IntToPtr(*u.MaxParallel)
   400  	}
   401  
   402  	if u.HealthCheck != nil {
   403  		copy.HealthCheck = helper.StringToPtr(*u.HealthCheck)
   404  	}
   405  
   406  	if u.MinHealthyTime != nil {
   407  		copy.MinHealthyTime = helper.TimeToPtr(*u.MinHealthyTime)
   408  	}
   409  
   410  	if u.HealthyDeadline != nil {
   411  		copy.HealthyDeadline = helper.TimeToPtr(*u.HealthyDeadline)
   412  	}
   413  
   414  	if u.ProgressDeadline != nil {
   415  		copy.ProgressDeadline = helper.TimeToPtr(*u.ProgressDeadline)
   416  	}
   417  
   418  	if u.AutoRevert != nil {
   419  		copy.AutoRevert = helper.BoolToPtr(*u.AutoRevert)
   420  	}
   421  
   422  	if u.Canary != nil {
   423  		copy.Canary = helper.IntToPtr(*u.Canary)
   424  	}
   425  
   426  	return copy
   427  }
   428  
   429  func (u *UpdateStrategy) Merge(o *UpdateStrategy) {
   430  	if o == nil {
   431  		return
   432  	}
   433  
   434  	if o.Stagger != nil {
   435  		u.Stagger = helper.TimeToPtr(*o.Stagger)
   436  	}
   437  
   438  	if o.MaxParallel != nil {
   439  		u.MaxParallel = helper.IntToPtr(*o.MaxParallel)
   440  	}
   441  
   442  	if o.HealthCheck != nil {
   443  		u.HealthCheck = helper.StringToPtr(*o.HealthCheck)
   444  	}
   445  
   446  	if o.MinHealthyTime != nil {
   447  		u.MinHealthyTime = helper.TimeToPtr(*o.MinHealthyTime)
   448  	}
   449  
   450  	if o.HealthyDeadline != nil {
   451  		u.HealthyDeadline = helper.TimeToPtr(*o.HealthyDeadline)
   452  	}
   453  
   454  	if o.ProgressDeadline != nil {
   455  		u.ProgressDeadline = helper.TimeToPtr(*o.ProgressDeadline)
   456  	}
   457  
   458  	if o.AutoRevert != nil {
   459  		u.AutoRevert = helper.BoolToPtr(*o.AutoRevert)
   460  	}
   461  
   462  	if o.Canary != nil {
   463  		u.Canary = helper.IntToPtr(*o.Canary)
   464  	}
   465  }
   466  
   467  func (u *UpdateStrategy) Canonicalize() {
   468  	d := DefaultUpdateStrategy()
   469  
   470  	if u.MaxParallel == nil {
   471  		u.MaxParallel = d.MaxParallel
   472  	}
   473  
   474  	if u.Stagger == nil {
   475  		u.Stagger = d.Stagger
   476  	}
   477  
   478  	if u.HealthCheck == nil {
   479  		u.HealthCheck = d.HealthCheck
   480  	}
   481  
   482  	if u.HealthyDeadline == nil {
   483  		u.HealthyDeadline = d.HealthyDeadline
   484  	}
   485  
   486  	if u.ProgressDeadline == nil {
   487  		u.ProgressDeadline = d.ProgressDeadline
   488  	}
   489  
   490  	if u.MinHealthyTime == nil {
   491  		u.MinHealthyTime = d.MinHealthyTime
   492  	}
   493  
   494  	if u.AutoRevert == nil {
   495  		u.AutoRevert = d.AutoRevert
   496  	}
   497  
   498  	if u.Canary == nil {
   499  		u.Canary = d.Canary
   500  	}
   501  }
   502  
   503  // Empty returns whether the UpdateStrategy is empty or has user defined values.
   504  func (u *UpdateStrategy) Empty() bool {
   505  	if u == nil {
   506  		return true
   507  	}
   508  
   509  	if u.Stagger != nil && *u.Stagger != 0 {
   510  		return false
   511  	}
   512  
   513  	if u.MaxParallel != nil && *u.MaxParallel != 0 {
   514  		return false
   515  	}
   516  
   517  	if u.HealthCheck != nil && *u.HealthCheck != "" {
   518  		return false
   519  	}
   520  
   521  	if u.MinHealthyTime != nil && *u.MinHealthyTime != 0 {
   522  		return false
   523  	}
   524  
   525  	if u.HealthyDeadline != nil && *u.HealthyDeadline != 0 {
   526  		return false
   527  	}
   528  
   529  	if u.ProgressDeadline != nil && *u.ProgressDeadline != 0 {
   530  		return false
   531  	}
   532  
   533  	if u.AutoRevert != nil && *u.AutoRevert {
   534  		return false
   535  	}
   536  
   537  	if u.Canary != nil && *u.Canary != 0 {
   538  		return false
   539  	}
   540  
   541  	return true
   542  }
   543  
   544  // PeriodicConfig is for serializing periodic config for a job.
   545  type PeriodicConfig struct {
   546  	Enabled         *bool
   547  	Spec            *string
   548  	SpecType        *string
   549  	ProhibitOverlap *bool   `mapstructure:"prohibit_overlap"`
   550  	TimeZone        *string `mapstructure:"time_zone"`
   551  }
   552  
   553  func (p *PeriodicConfig) Canonicalize() {
   554  	if p.Enabled == nil {
   555  		p.Enabled = helper.BoolToPtr(true)
   556  	}
   557  	if p.Spec == nil {
   558  		p.Spec = helper.StringToPtr("")
   559  	}
   560  	if p.SpecType == nil {
   561  		p.SpecType = helper.StringToPtr(PeriodicSpecCron)
   562  	}
   563  	if p.ProhibitOverlap == nil {
   564  		p.ProhibitOverlap = helper.BoolToPtr(false)
   565  	}
   566  	if p.TimeZone == nil || *p.TimeZone == "" {
   567  		p.TimeZone = helper.StringToPtr("UTC")
   568  	}
   569  }
   570  
   571  // Next returns the closest time instant matching the spec that is after the
   572  // passed time. If no matching instance exists, the zero value of time.Time is
   573  // returned. The `time.Location` of the returned value matches that of the
   574  // passed time.
   575  func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) {
   576  	if *p.SpecType == PeriodicSpecCron {
   577  		if e, err := cronexpr.Parse(*p.Spec); err == nil {
   578  			return structs.CronParseNext(e, fromTime, *p.Spec)
   579  		}
   580  	}
   581  
   582  	return time.Time{}, nil
   583  }
   584  
   585  func (p *PeriodicConfig) GetLocation() (*time.Location, error) {
   586  	if p.TimeZone == nil || *p.TimeZone == "" {
   587  		return time.UTC, nil
   588  	}
   589  
   590  	return time.LoadLocation(*p.TimeZone)
   591  }
   592  
   593  // ParameterizedJobConfig is used to configure the parameterized job.
   594  type ParameterizedJobConfig struct {
   595  	Payload      string
   596  	MetaRequired []string `mapstructure:"meta_required"`
   597  	MetaOptional []string `mapstructure:"meta_optional"`
   598  }
   599  
   600  // Job is used to serialize a job.
   601  type Job struct {
   602  	Stop              *bool
   603  	Region            *string
   604  	Namespace         *string
   605  	ID                *string
   606  	ParentID          *string
   607  	Name              *string
   608  	Type              *string
   609  	Priority          *int
   610  	AllAtOnce         *bool `mapstructure:"all_at_once"`
   611  	Datacenters       []string
   612  	Constraints       []*Constraint
   613  	Affinities        []*Affinity
   614  	TaskGroups        []*TaskGroup
   615  	Update            *UpdateStrategy
   616  	Spreads           []*Spread
   617  	Periodic          *PeriodicConfig
   618  	ParameterizedJob  *ParameterizedJobConfig
   619  	Dispatched        bool
   620  	Payload           []byte
   621  	Reschedule        *ReschedulePolicy
   622  	Migrate           *MigrateStrategy
   623  	Meta              map[string]string
   624  	VaultToken        *string `mapstructure:"vault_token"`
   625  	Status            *string
   626  	StatusDescription *string
   627  	Stable            *bool
   628  	Version           *uint64
   629  	SubmitTime        *int64
   630  	CreateIndex       *uint64
   631  	ModifyIndex       *uint64
   632  	JobModifyIndex    *uint64
   633  }
   634  
   635  // IsPeriodic returns whether a job is periodic.
   636  func (j *Job) IsPeriodic() bool {
   637  	return j.Periodic != nil
   638  }
   639  
   640  // IsParameterized returns whether a job is parameterized job.
   641  func (j *Job) IsParameterized() bool {
   642  	return j.ParameterizedJob != nil && !j.Dispatched
   643  }
   644  
   645  func (j *Job) Canonicalize() {
   646  	if j.ID == nil {
   647  		j.ID = helper.StringToPtr("")
   648  	}
   649  	if j.Name == nil {
   650  		j.Name = helper.StringToPtr(*j.ID)
   651  	}
   652  	if j.ParentID == nil {
   653  		j.ParentID = helper.StringToPtr("")
   654  	}
   655  	if j.Namespace == nil {
   656  		j.Namespace = helper.StringToPtr(DefaultNamespace)
   657  	}
   658  	if j.Priority == nil {
   659  		j.Priority = helper.IntToPtr(50)
   660  	}
   661  	if j.Stop == nil {
   662  		j.Stop = helper.BoolToPtr(false)
   663  	}
   664  	if j.Region == nil {
   665  		j.Region = helper.StringToPtr("global")
   666  	}
   667  	if j.Namespace == nil {
   668  		j.Namespace = helper.StringToPtr("default")
   669  	}
   670  	if j.Type == nil {
   671  		j.Type = helper.StringToPtr("service")
   672  	}
   673  	if j.AllAtOnce == nil {
   674  		j.AllAtOnce = helper.BoolToPtr(false)
   675  	}
   676  	if j.VaultToken == nil {
   677  		j.VaultToken = helper.StringToPtr("")
   678  	}
   679  	if j.Status == nil {
   680  		j.Status = helper.StringToPtr("")
   681  	}
   682  	if j.StatusDescription == nil {
   683  		j.StatusDescription = helper.StringToPtr("")
   684  	}
   685  	if j.Stable == nil {
   686  		j.Stable = helper.BoolToPtr(false)
   687  	}
   688  	if j.Version == nil {
   689  		j.Version = helper.Uint64ToPtr(0)
   690  	}
   691  	if j.CreateIndex == nil {
   692  		j.CreateIndex = helper.Uint64ToPtr(0)
   693  	}
   694  	if j.ModifyIndex == nil {
   695  		j.ModifyIndex = helper.Uint64ToPtr(0)
   696  	}
   697  	if j.JobModifyIndex == nil {
   698  		j.JobModifyIndex = helper.Uint64ToPtr(0)
   699  	}
   700  	if j.Periodic != nil {
   701  		j.Periodic.Canonicalize()
   702  	}
   703  	if j.Update != nil {
   704  		j.Update.Canonicalize()
   705  	}
   706  
   707  	for _, tg := range j.TaskGroups {
   708  		tg.Canonicalize(j)
   709  	}
   710  }
   711  
   712  // LookupTaskGroup finds a task group by name
   713  func (j *Job) LookupTaskGroup(name string) *TaskGroup {
   714  	for _, tg := range j.TaskGroups {
   715  		if *tg.Name == name {
   716  			return tg
   717  		}
   718  	}
   719  	return nil
   720  }
   721  
   722  // JobSummary summarizes the state of the allocations of a job
   723  type JobSummary struct {
   724  	JobID     string
   725  	Namespace string
   726  	Summary   map[string]TaskGroupSummary
   727  	Children  *JobChildrenSummary
   728  
   729  	// Raft Indexes
   730  	CreateIndex uint64
   731  	ModifyIndex uint64
   732  }
   733  
   734  // JobChildrenSummary contains the summary of children job status
   735  type JobChildrenSummary struct {
   736  	Pending int64
   737  	Running int64
   738  	Dead    int64
   739  }
   740  
   741  func (jc *JobChildrenSummary) Sum() int {
   742  	if jc == nil {
   743  		return 0
   744  	}
   745  
   746  	return int(jc.Pending + jc.Running + jc.Dead)
   747  }
   748  
   749  // TaskGroup summarizes the state of all the allocations of a particular
   750  // TaskGroup
   751  type TaskGroupSummary struct {
   752  	Queued   int
   753  	Complete int
   754  	Failed   int
   755  	Running  int
   756  	Starting int
   757  	Lost     int
   758  }
   759  
   760  // JobListStub is used to return a subset of information about
   761  // jobs during list operations.
   762  type JobListStub struct {
   763  	ID                string
   764  	ParentID          string
   765  	Name              string
   766  	Type              string
   767  	Priority          int
   768  	Periodic          bool
   769  	ParameterizedJob  bool
   770  	Stop              bool
   771  	Status            string
   772  	StatusDescription string
   773  	JobSummary        *JobSummary
   774  	CreateIndex       uint64
   775  	ModifyIndex       uint64
   776  	JobModifyIndex    uint64
   777  	SubmitTime        int64
   778  }
   779  
   780  // JobIDSort is used to sort jobs by their job ID's.
   781  type JobIDSort []*JobListStub
   782  
   783  func (j JobIDSort) Len() int {
   784  	return len(j)
   785  }
   786  
   787  func (j JobIDSort) Less(a, b int) bool {
   788  	return j[a].ID < j[b].ID
   789  }
   790  
   791  func (j JobIDSort) Swap(a, b int) {
   792  	j[a], j[b] = j[b], j[a]
   793  }
   794  
   795  // NewServiceJob creates and returns a new service-style job
   796  // for long-lived processes using the provided name, ID, and
   797  // relative job priority.
   798  func NewServiceJob(id, name, region string, pri int) *Job {
   799  	return newJob(id, name, region, JobTypeService, pri)
   800  }
   801  
   802  // NewBatchJob creates and returns a new batch-style job for
   803  // short-lived processes using the provided name and ID along
   804  // with the relative job priority.
   805  func NewBatchJob(id, name, region string, pri int) *Job {
   806  	return newJob(id, name, region, JobTypeBatch, pri)
   807  }
   808  
   809  // newJob is used to create a new Job struct.
   810  func newJob(id, name, region, typ string, pri int) *Job {
   811  	return &Job{
   812  		Region:   &region,
   813  		ID:       &id,
   814  		Name:     &name,
   815  		Type:     &typ,
   816  		Priority: &pri,
   817  	}
   818  }
   819  
   820  // SetMeta is used to set arbitrary k/v pairs of metadata on a job.
   821  func (j *Job) SetMeta(key, val string) *Job {
   822  	if j.Meta == nil {
   823  		j.Meta = make(map[string]string)
   824  	}
   825  	j.Meta[key] = val
   826  	return j
   827  }
   828  
   829  // AddDatacenter is used to add a datacenter to a job.
   830  func (j *Job) AddDatacenter(dc string) *Job {
   831  	j.Datacenters = append(j.Datacenters, dc)
   832  	return j
   833  }
   834  
   835  // Constrain is used to add a constraint to a job.
   836  func (j *Job) Constrain(c *Constraint) *Job {
   837  	j.Constraints = append(j.Constraints, c)
   838  	return j
   839  }
   840  
   841  // AddAffinity is used to add an affinity to a job.
   842  func (j *Job) AddAffinity(a *Affinity) *Job {
   843  	j.Affinities = append(j.Affinities, a)
   844  	return j
   845  }
   846  
   847  // AddTaskGroup adds a task group to an existing job.
   848  func (j *Job) AddTaskGroup(grp *TaskGroup) *Job {
   849  	j.TaskGroups = append(j.TaskGroups, grp)
   850  	return j
   851  }
   852  
   853  // AddPeriodicConfig adds a periodic config to an existing job.
   854  func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job {
   855  	j.Periodic = cfg
   856  	return j
   857  }
   858  
   859  func (j *Job) AddSpread(s *Spread) *Job {
   860  	j.Spreads = append(j.Spreads, s)
   861  	return j
   862  }
   863  
   864  type WriteRequest struct {
   865  	// The target region for this write
   866  	Region string
   867  
   868  	// Namespace is the target namespace for this write
   869  	Namespace string
   870  
   871  	// SecretID is the secret ID of an ACL token
   872  	SecretID string
   873  }
   874  
   875  // JobValidateRequest is used to validate a job
   876  type JobValidateRequest struct {
   877  	Job *Job
   878  	WriteRequest
   879  }
   880  
   881  // JobValidateResponse is the response from validate request
   882  type JobValidateResponse struct {
   883  	// DriverConfigValidated indicates whether the agent validated the driver
   884  	// config
   885  	DriverConfigValidated bool
   886  
   887  	// ValidationErrors is a list of validation errors
   888  	ValidationErrors []string
   889  
   890  	// Error is a string version of any error that may have occurred
   891  	Error string
   892  
   893  	// Warnings contains any warnings about the given job. These may include
   894  	// deprecation warnings.
   895  	Warnings string
   896  }
   897  
   898  // JobRevertRequest is used to revert a job to a prior version.
   899  type JobRevertRequest struct {
   900  	// JobID is the ID of the job  being reverted
   901  	JobID string
   902  
   903  	// JobVersion the version to revert to.
   904  	JobVersion uint64
   905  
   906  	// EnforcePriorVersion if set will enforce that the job is at the given
   907  	// version before reverting.
   908  	EnforcePriorVersion *uint64
   909  
   910  	WriteRequest
   911  }
   912  
   913  // JobUpdateRequest is used to update a job
   914  type JobRegisterRequest struct {
   915  	Job *Job
   916  	// If EnforceIndex is set then the job will only be registered if the passed
   917  	// JobModifyIndex matches the current Jobs index. If the index is zero, the
   918  	// register only occurs if the job is new.
   919  	EnforceIndex   bool
   920  	JobModifyIndex uint64
   921  	PolicyOverride bool
   922  
   923  	WriteRequest
   924  }
   925  
   926  // RegisterJobRequest is used to serialize a job registration
   927  type RegisterJobRequest struct {
   928  	Job            *Job
   929  	EnforceIndex   bool   `json:",omitempty"`
   930  	JobModifyIndex uint64 `json:",omitempty"`
   931  	PolicyOverride bool   `json:",omitempty"`
   932  }
   933  
   934  // JobRegisterResponse is used to respond to a job registration
   935  type JobRegisterResponse struct {
   936  	EvalID          string
   937  	EvalCreateIndex uint64
   938  	JobModifyIndex  uint64
   939  
   940  	// Warnings contains any warnings about the given job. These may include
   941  	// deprecation warnings.
   942  	Warnings string
   943  
   944  	QueryMeta
   945  }
   946  
   947  // JobDeregisterResponse is used to respond to a job deregistration
   948  type JobDeregisterResponse struct {
   949  	EvalID          string
   950  	EvalCreateIndex uint64
   951  	JobModifyIndex  uint64
   952  	QueryMeta
   953  }
   954  
   955  type JobPlanRequest struct {
   956  	Job            *Job
   957  	Diff           bool
   958  	PolicyOverride bool
   959  	WriteRequest
   960  }
   961  
   962  type JobPlanResponse struct {
   963  	JobModifyIndex     uint64
   964  	CreatedEvals       []*Evaluation
   965  	Diff               *JobDiff
   966  	Annotations        *PlanAnnotations
   967  	FailedTGAllocs     map[string]*AllocationMetric
   968  	NextPeriodicLaunch time.Time
   969  
   970  	// Warnings contains any warnings about the given job. These may include
   971  	// deprecation warnings.
   972  	Warnings string
   973  }
   974  
   975  type JobDiff struct {
   976  	Type       string
   977  	ID         string
   978  	Fields     []*FieldDiff
   979  	Objects    []*ObjectDiff
   980  	TaskGroups []*TaskGroupDiff
   981  }
   982  
   983  type TaskGroupDiff struct {
   984  	Type    string
   985  	Name    string
   986  	Fields  []*FieldDiff
   987  	Objects []*ObjectDiff
   988  	Tasks   []*TaskDiff
   989  	Updates map[string]uint64
   990  }
   991  
   992  type TaskDiff struct {
   993  	Type        string
   994  	Name        string
   995  	Fields      []*FieldDiff
   996  	Objects     []*ObjectDiff
   997  	Annotations []string
   998  }
   999  
  1000  type FieldDiff struct {
  1001  	Type        string
  1002  	Name        string
  1003  	Old, New    string
  1004  	Annotations []string
  1005  }
  1006  
  1007  type ObjectDiff struct {
  1008  	Type    string
  1009  	Name    string
  1010  	Fields  []*FieldDiff
  1011  	Objects []*ObjectDiff
  1012  }
  1013  
  1014  type PlanAnnotations struct {
  1015  	DesiredTGUpdates map[string]*DesiredUpdates
  1016  }
  1017  
  1018  type DesiredUpdates struct {
  1019  	Ignore            uint64
  1020  	Place             uint64
  1021  	Migrate           uint64
  1022  	Stop              uint64
  1023  	InPlaceUpdate     uint64
  1024  	DestructiveUpdate uint64
  1025  	Canary            uint64
  1026  }
  1027  
  1028  type JobDispatchRequest struct {
  1029  	JobID   string
  1030  	Payload []byte
  1031  	Meta    map[string]string
  1032  }
  1033  
  1034  type JobDispatchResponse struct {
  1035  	DispatchedJobID string
  1036  	EvalID          string
  1037  	EvalCreateIndex uint64
  1038  	JobCreateIndex  uint64
  1039  	WriteMeta
  1040  }
  1041  
  1042  // JobVersionsResponse is used for a job get versions request
  1043  type JobVersionsResponse struct {
  1044  	Versions []*Job
  1045  	Diffs    []*JobDiff
  1046  	QueryMeta
  1047  }
  1048  
  1049  // JobStabilityRequest is used to marked a job as stable.
  1050  type JobStabilityRequest struct {
  1051  	// Job to set the stability on
  1052  	JobID      string
  1053  	JobVersion uint64
  1054  
  1055  	// Set the stability
  1056  	Stable bool
  1057  	WriteRequest
  1058  }
  1059  
  1060  // JobStabilityResponse is the response when marking a job as stable.
  1061  type JobStabilityResponse struct {
  1062  	JobModifyIndex uint64
  1063  	WriteMeta
  1064  }
  1065  
  1066  // JobEvaluateRequest is used when we just need to re-evaluate a target job
  1067  type JobEvaluateRequest struct {
  1068  	JobID       string
  1069  	EvalOptions EvalOptions
  1070  	WriteRequest
  1071  }
  1072  
  1073  // EvalOptions is used to encapsulate options when forcing a job evaluation
  1074  type EvalOptions struct {
  1075  	ForceReschedule bool
  1076  }