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