github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/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  	tg.ReschedulePolicy = &structs.ReschedulePolicy{
   642  		Attempts: *taskGroup.ReschedulePolicy.Attempts,
   643  		Interval: *taskGroup.ReschedulePolicy.Interval,
   644  	}
   645  
   646  	tg.EphemeralDisk = &structs.EphemeralDisk{
   647  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   648  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   649  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   650  	}
   651  
   652  	if taskGroup.Update != nil {
   653  		tg.Update = &structs.UpdateStrategy{
   654  			Stagger:         *taskGroup.Update.Stagger,
   655  			MaxParallel:     *taskGroup.Update.MaxParallel,
   656  			HealthCheck:     *taskGroup.Update.HealthCheck,
   657  			MinHealthyTime:  *taskGroup.Update.MinHealthyTime,
   658  			HealthyDeadline: *taskGroup.Update.HealthyDeadline,
   659  			AutoRevert:      *taskGroup.Update.AutoRevert,
   660  			Canary:          *taskGroup.Update.Canary,
   661  		}
   662  	}
   663  
   664  	if l := len(taskGroup.Tasks); l != 0 {
   665  		tg.Tasks = make([]*structs.Task, l)
   666  		for l, task := range taskGroup.Tasks {
   667  			t := &structs.Task{}
   668  			ApiTaskToStructsTask(task, t)
   669  			tg.Tasks[l] = t
   670  		}
   671  	}
   672  }
   673  
   674  // ApiTaskToStructsTask is a copy and type conversion between the API
   675  // representation of a task from a struct representation of a task.
   676  func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
   677  	structsTask.Name = apiTask.Name
   678  	structsTask.Driver = apiTask.Driver
   679  	structsTask.User = apiTask.User
   680  	structsTask.Leader = apiTask.Leader
   681  	structsTask.Config = apiTask.Config
   682  	structsTask.Env = apiTask.Env
   683  	structsTask.Meta = apiTask.Meta
   684  	structsTask.KillTimeout = *apiTask.KillTimeout
   685  	structsTask.ShutdownDelay = apiTask.ShutdownDelay
   686  	structsTask.KillSignal = apiTask.KillSignal
   687  
   688  	if l := len(apiTask.Constraints); l != 0 {
   689  		structsTask.Constraints = make([]*structs.Constraint, l)
   690  		for i, constraint := range apiTask.Constraints {
   691  			c := &structs.Constraint{}
   692  			ApiConstraintToStructs(constraint, c)
   693  			structsTask.Constraints[i] = c
   694  		}
   695  	}
   696  
   697  	if l := len(apiTask.Services); l != 0 {
   698  		structsTask.Services = make([]*structs.Service, l)
   699  		for i, service := range apiTask.Services {
   700  			structsTask.Services[i] = &structs.Service{
   701  				Name:        service.Name,
   702  				PortLabel:   service.PortLabel,
   703  				Tags:        service.Tags,
   704  				AddressMode: service.AddressMode,
   705  			}
   706  
   707  			if l := len(service.Checks); l != 0 {
   708  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   709  				for j, check := range service.Checks {
   710  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   711  						Name:          check.Name,
   712  						Type:          check.Type,
   713  						Command:       check.Command,
   714  						Args:          check.Args,
   715  						Path:          check.Path,
   716  						Protocol:      check.Protocol,
   717  						PortLabel:     check.PortLabel,
   718  						AddressMode:   check.AddressMode,
   719  						Interval:      check.Interval,
   720  						Timeout:       check.Timeout,
   721  						InitialStatus: check.InitialStatus,
   722  						TLSSkipVerify: check.TLSSkipVerify,
   723  						Header:        check.Header,
   724  						Method:        check.Method,
   725  					}
   726  					if check.CheckRestart != nil {
   727  						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
   728  							Limit:          check.CheckRestart.Limit,
   729  							Grace:          *check.CheckRestart.Grace,
   730  							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
   731  						}
   732  					}
   733  				}
   734  			}
   735  		}
   736  	}
   737  
   738  	structsTask.Resources = &structs.Resources{
   739  		CPU:      *apiTask.Resources.CPU,
   740  		MemoryMB: *apiTask.Resources.MemoryMB,
   741  		IOPS:     *apiTask.Resources.IOPS,
   742  	}
   743  
   744  	if l := len(apiTask.Resources.Networks); l != 0 {
   745  		structsTask.Resources.Networks = make([]*structs.NetworkResource, l)
   746  		for i, nw := range apiTask.Resources.Networks {
   747  			structsTask.Resources.Networks[i] = &structs.NetworkResource{
   748  				CIDR:  nw.CIDR,
   749  				IP:    nw.IP,
   750  				MBits: *nw.MBits,
   751  			}
   752  
   753  			if l := len(nw.DynamicPorts); l != 0 {
   754  				structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, l)
   755  				for j, dp := range nw.DynamicPorts {
   756  					structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{
   757  						Label: dp.Label,
   758  						Value: dp.Value,
   759  					}
   760  				}
   761  			}
   762  
   763  			if l := len(nw.ReservedPorts); l != 0 {
   764  				structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, l)
   765  				for j, rp := range nw.ReservedPorts {
   766  					structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{
   767  						Label: rp.Label,
   768  						Value: rp.Value,
   769  					}
   770  				}
   771  			}
   772  		}
   773  	}
   774  
   775  	structsTask.LogConfig = &structs.LogConfig{
   776  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   777  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   778  	}
   779  
   780  	if l := len(apiTask.Artifacts); l != 0 {
   781  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   782  		for k, ta := range apiTask.Artifacts {
   783  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   784  				GetterSource:  *ta.GetterSource,
   785  				GetterOptions: ta.GetterOptions,
   786  				GetterMode:    *ta.GetterMode,
   787  				RelativeDest:  *ta.RelativeDest,
   788  			}
   789  		}
   790  	}
   791  
   792  	if apiTask.Vault != nil {
   793  		structsTask.Vault = &structs.Vault{
   794  			Policies:     apiTask.Vault.Policies,
   795  			Env:          *apiTask.Vault.Env,
   796  			ChangeMode:   *apiTask.Vault.ChangeMode,
   797  			ChangeSignal: *apiTask.Vault.ChangeSignal,
   798  		}
   799  	}
   800  
   801  	if l := len(apiTask.Templates); l != 0 {
   802  		structsTask.Templates = make([]*structs.Template, l)
   803  		for i, template := range apiTask.Templates {
   804  			structsTask.Templates[i] = &structs.Template{
   805  				SourcePath:   *template.SourcePath,
   806  				DestPath:     *template.DestPath,
   807  				EmbeddedTmpl: *template.EmbeddedTmpl,
   808  				ChangeMode:   *template.ChangeMode,
   809  				ChangeSignal: *template.ChangeSignal,
   810  				Splay:        *template.Splay,
   811  				Perms:        *template.Perms,
   812  				LeftDelim:    *template.LeftDelim,
   813  				RightDelim:   *template.RightDelim,
   814  				Envvars:      *template.Envvars,
   815  				VaultGrace:   *template.VaultGrace,
   816  			}
   817  		}
   818  	}
   819  
   820  	if apiTask.DispatchPayload != nil {
   821  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
   822  			File: apiTask.DispatchPayload.File,
   823  		}
   824  	}
   825  }
   826  
   827  func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) {
   828  	c2.LTarget = c1.LTarget
   829  	c2.RTarget = c1.RTarget
   830  	c2.Operand = c1.Operand
   831  }