github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/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  	default:
    74  		return s.jobCRUD(resp, req, path)
    75  	}
    76  }
    77  
    78  func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Request,
    79  	jobName string) (interface{}, error) {
    80  	if req.Method != "PUT" && req.Method != "POST" {
    81  		return nil, CodedError(405, ErrInvalidMethod)
    82  	}
    83  	args := structs.JobEvaluateRequest{
    84  		JobID: jobName,
    85  	}
    86  	s.parseRegion(req, &args.Region)
    87  
    88  	var out structs.JobRegisterResponse
    89  	if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil {
    90  		return nil, err
    91  	}
    92  	setIndex(resp, out.Index)
    93  	return out, nil
    94  }
    95  
    96  func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request,
    97  	jobName string) (interface{}, error) {
    98  	if req.Method != "PUT" && req.Method != "POST" {
    99  		return nil, CodedError(405, ErrInvalidMethod)
   100  	}
   101  
   102  	var args api.JobPlanRequest
   103  	if err := decodeBody(req, &args); err != nil {
   104  		return nil, CodedError(400, err.Error())
   105  	}
   106  	if args.Job == nil {
   107  		return nil, CodedError(400, "Job must be specified")
   108  	}
   109  	if args.Job.ID == nil {
   110  		return nil, CodedError(400, "Job must have a valid ID")
   111  	}
   112  	if jobName != "" && *args.Job.ID != jobName {
   113  		return nil, CodedError(400, "Job ID does not match")
   114  	}
   115  	s.parseRegion(req, &args.Region)
   116  
   117  	sJob := ApiJobToStructJob(args.Job)
   118  	planReq := structs.JobPlanRequest{
   119  		Job:  sJob,
   120  		Diff: args.Diff,
   121  		WriteRequest: structs.WriteRequest{
   122  			Region: args.WriteRequest.Region,
   123  		},
   124  	}
   125  	var out structs.JobPlanResponse
   126  	if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil {
   127  		return nil, err
   128  	}
   129  	setIndex(resp, out.Index)
   130  	return out, nil
   131  }
   132  
   133  func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   134  	// Ensure request method is POST or PUT
   135  	if !(req.Method == "POST" || req.Method == "PUT") {
   136  		return nil, CodedError(405, ErrInvalidMethod)
   137  	}
   138  
   139  	var validateRequest api.JobValidateRequest
   140  	if err := decodeBody(req, &validateRequest); err != nil {
   141  		return nil, CodedError(400, err.Error())
   142  	}
   143  	if validateRequest.Job == nil {
   144  		return nil, CodedError(400, "Job must be specified")
   145  	}
   146  
   147  	job := ApiJobToStructJob(validateRequest.Job)
   148  	args := structs.JobValidateRequest{
   149  		Job: job,
   150  		WriteRequest: structs.WriteRequest{
   151  			Region: validateRequest.Region,
   152  		},
   153  	}
   154  	s.parseRegion(req, &args.Region)
   155  
   156  	var out structs.JobValidateResponse
   157  	if err := s.agent.RPC("Job.Validate", &args, &out); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	return out, nil
   162  }
   163  
   164  func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request,
   165  	jobName string) (interface{}, error) {
   166  	if req.Method != "PUT" && req.Method != "POST" {
   167  		return nil, CodedError(405, ErrInvalidMethod)
   168  	}
   169  
   170  	args := structs.PeriodicForceRequest{
   171  		JobID: jobName,
   172  	}
   173  	s.parseRegion(req, &args.Region)
   174  
   175  	var out structs.PeriodicForceResponse
   176  	if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil {
   177  		return nil, err
   178  	}
   179  	setIndex(resp, out.Index)
   180  	return out, nil
   181  }
   182  
   183  func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request,
   184  	jobName string) (interface{}, error) {
   185  	if req.Method != "GET" {
   186  		return nil, CodedError(405, ErrInvalidMethod)
   187  	}
   188  	allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all"))
   189  
   190  	args := structs.JobSpecificRequest{
   191  		JobID:     jobName,
   192  		AllAllocs: allAllocs,
   193  	}
   194  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   195  		return nil, nil
   196  	}
   197  
   198  	var out structs.JobAllocationsResponse
   199  	if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	setMeta(resp, &out.QueryMeta)
   204  	if out.Allocations == nil {
   205  		out.Allocations = make([]*structs.AllocListStub, 0)
   206  	}
   207  	return out.Allocations, nil
   208  }
   209  
   210  func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request,
   211  	jobName string) (interface{}, error) {
   212  	if req.Method != "GET" {
   213  		return nil, CodedError(405, ErrInvalidMethod)
   214  	}
   215  	args := structs.JobSpecificRequest{
   216  		JobID: jobName,
   217  	}
   218  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   219  		return nil, nil
   220  	}
   221  
   222  	var out structs.JobEvaluationsResponse
   223  	if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	setMeta(resp, &out.QueryMeta)
   228  	if out.Evaluations == nil {
   229  		out.Evaluations = make([]*structs.Evaluation, 0)
   230  	}
   231  	return out.Evaluations, nil
   232  }
   233  
   234  func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request,
   235  	jobName string) (interface{}, error) {
   236  	switch req.Method {
   237  	case "GET":
   238  		return s.jobQuery(resp, req, jobName)
   239  	case "PUT", "POST":
   240  		return s.jobUpdate(resp, req, jobName)
   241  	case "DELETE":
   242  		return s.jobDelete(resp, req, jobName)
   243  	default:
   244  		return nil, CodedError(405, ErrInvalidMethod)
   245  	}
   246  }
   247  
   248  func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request,
   249  	jobName string) (interface{}, error) {
   250  	args := structs.JobSpecificRequest{
   251  		JobID: jobName,
   252  	}
   253  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   254  		return nil, nil
   255  	}
   256  
   257  	var out structs.SingleJobResponse
   258  	if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	setMeta(resp, &out.QueryMeta)
   263  	if out.Job == nil {
   264  		return nil, CodedError(404, "job not found")
   265  	}
   266  
   267  	// Decode the payload if there is any
   268  	job := out.Job
   269  	if len(job.Payload) != 0 {
   270  		decoded, err := snappy.Decode(nil, out.Job.Payload)
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  		job = job.Copy()
   275  		job.Payload = decoded
   276  	}
   277  
   278  	return job, nil
   279  }
   280  
   281  func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request,
   282  	jobName string) (interface{}, error) {
   283  	var args api.JobRegisterRequest
   284  	if err := decodeBody(req, &args); err != nil {
   285  		return nil, CodedError(400, err.Error())
   286  	}
   287  	if args.Job == nil {
   288  		return nil, CodedError(400, "Job must be specified")
   289  	}
   290  
   291  	if args.Job.ID == nil {
   292  		return nil, CodedError(400, "Job ID hasn't been provided")
   293  	}
   294  	if jobName != "" && *args.Job.ID != jobName {
   295  		return nil, CodedError(400, "Job ID does not match name")
   296  	}
   297  	s.parseRegion(req, &args.Region)
   298  
   299  	sJob := ApiJobToStructJob(args.Job)
   300  
   301  	regReq := structs.JobRegisterRequest{
   302  		Job:            sJob,
   303  		EnforceIndex:   args.EnforceIndex,
   304  		JobModifyIndex: args.JobModifyIndex,
   305  		WriteRequest: structs.WriteRequest{
   306  			Region: args.WriteRequest.Region,
   307  		},
   308  	}
   309  	var out structs.JobRegisterResponse
   310  	if err := s.agent.RPC("Job.Register", &regReq, &out); err != nil {
   311  		return nil, err
   312  	}
   313  	setIndex(resp, out.Index)
   314  	return out, nil
   315  }
   316  
   317  func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request,
   318  	jobName string) (interface{}, error) {
   319  
   320  	purgeStr := req.URL.Query().Get("purge")
   321  	var purgeBool bool
   322  	if purgeStr != "" {
   323  		var err error
   324  		purgeBool, err = strconv.ParseBool(purgeStr)
   325  		if err != nil {
   326  			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err)
   327  		}
   328  	}
   329  
   330  	args := structs.JobDeregisterRequest{
   331  		JobID: jobName,
   332  		Purge: purgeBool,
   333  	}
   334  	s.parseRegion(req, &args.Region)
   335  
   336  	var out structs.JobDeregisterResponse
   337  	if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil {
   338  		return nil, err
   339  	}
   340  	setIndex(resp, out.Index)
   341  	return out, nil
   342  }
   343  
   344  func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request,
   345  	jobName string) (interface{}, error) {
   346  	args := structs.JobSpecificRequest{
   347  		JobID: jobName,
   348  	}
   349  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   350  		return nil, nil
   351  	}
   352  
   353  	var out structs.JobVersionsResponse
   354  	if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil {
   355  		return nil, err
   356  	}
   357  
   358  	setMeta(resp, &out.QueryMeta)
   359  	if len(out.Versions) == 0 {
   360  		return nil, CodedError(404, "job versions not found")
   361  	}
   362  
   363  	return out.Versions, nil
   364  }
   365  
   366  func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request,
   367  	jobName string) (interface{}, error) {
   368  
   369  	if req.Method != "PUT" && req.Method != "POST" {
   370  		return nil, CodedError(405, ErrInvalidMethod)
   371  	}
   372  
   373  	var revertRequest structs.JobRevertRequest
   374  	if err := decodeBody(req, &revertRequest); err != nil {
   375  		return nil, CodedError(400, err.Error())
   376  	}
   377  	if revertRequest.JobID == "" {
   378  		return nil, CodedError(400, "JobID must be specified")
   379  	}
   380  	if revertRequest.JobID != jobName {
   381  		return nil, CodedError(400, "Job ID does not match")
   382  	}
   383  
   384  	s.parseRegion(req, &revertRequest.Region)
   385  
   386  	var out structs.JobRegisterResponse
   387  	if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	setMeta(resp, &out.QueryMeta)
   392  	return out, nil
   393  }
   394  
   395  func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   396  	args := structs.JobSummaryRequest{
   397  		JobID: name,
   398  	}
   399  	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
   400  		return nil, nil
   401  	}
   402  
   403  	var out structs.JobSummaryResponse
   404  	if err := s.agent.RPC("Job.Summary", &args, &out); err != nil {
   405  		return nil, err
   406  	}
   407  
   408  	setMeta(resp, &out.QueryMeta)
   409  	if out.JobSummary == nil {
   410  		return nil, CodedError(404, "job not found")
   411  	}
   412  	setIndex(resp, out.Index)
   413  	return out.JobSummary, nil
   414  }
   415  
   416  func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
   417  	if req.Method != "PUT" && req.Method != "POST" {
   418  		return nil, CodedError(405, ErrInvalidMethod)
   419  	}
   420  	args := structs.JobDispatchRequest{}
   421  	if err := decodeBody(req, &args); err != nil {
   422  		return nil, CodedError(400, err.Error())
   423  	}
   424  	if args.JobID != "" && args.JobID != name {
   425  		return nil, CodedError(400, "Job ID does not match")
   426  	}
   427  	if args.JobID == "" {
   428  		args.JobID = name
   429  	}
   430  
   431  	s.parseRegion(req, &args.Region)
   432  
   433  	var out structs.JobDispatchResponse
   434  	if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil {
   435  		return nil, err
   436  	}
   437  	setIndex(resp, out.Index)
   438  	return out, nil
   439  }
   440  
   441  func ApiJobToStructJob(job *api.Job) *structs.Job {
   442  	job.Canonicalize()
   443  
   444  	j := &structs.Job{
   445  		Stop:        *job.Stop,
   446  		Region:      *job.Region,
   447  		ID:          *job.ID,
   448  		ParentID:    *job.ParentID,
   449  		Name:        *job.Name,
   450  		Type:        *job.Type,
   451  		Priority:    *job.Priority,
   452  		AllAtOnce:   *job.AllAtOnce,
   453  		Datacenters: job.Datacenters,
   454  		Payload:     job.Payload,
   455  		Meta:        job.Meta,
   456  		VaultToken:  *job.VaultToken,
   457  	}
   458  
   459  	if l := len(job.Constraints); l != 0 {
   460  		j.Constraints = make([]*structs.Constraint, l)
   461  		for i, c := range job.Constraints {
   462  			con := &structs.Constraint{}
   463  			ApiConstraintToStructs(c, con)
   464  			j.Constraints[i] = con
   465  		}
   466  	}
   467  
   468  	if job.Update != nil {
   469  		j.Update = structs.UpdateStrategy{
   470  			Stagger:     job.Update.Stagger,
   471  			MaxParallel: job.Update.MaxParallel,
   472  		}
   473  	}
   474  
   475  	if job.Periodic != nil {
   476  		j.Periodic = &structs.PeriodicConfig{
   477  			Enabled:         *job.Periodic.Enabled,
   478  			SpecType:        *job.Periodic.SpecType,
   479  			ProhibitOverlap: *job.Periodic.ProhibitOverlap,
   480  			TimeZone:        *job.Periodic.TimeZone,
   481  		}
   482  
   483  		if job.Periodic.Spec != nil {
   484  			j.Periodic.Spec = *job.Periodic.Spec
   485  		}
   486  	}
   487  
   488  	if job.ParameterizedJob != nil {
   489  		j.ParameterizedJob = &structs.ParameterizedJobConfig{
   490  			Payload:      job.ParameterizedJob.Payload,
   491  			MetaRequired: job.ParameterizedJob.MetaRequired,
   492  			MetaOptional: job.ParameterizedJob.MetaOptional,
   493  		}
   494  	}
   495  
   496  	if l := len(job.TaskGroups); l != 0 {
   497  		j.TaskGroups = make([]*structs.TaskGroup, l)
   498  		for i, taskGroup := range job.TaskGroups {
   499  			tg := &structs.TaskGroup{}
   500  			ApiTgToStructsTG(taskGroup, tg)
   501  			j.TaskGroups[i] = tg
   502  		}
   503  	}
   504  
   505  	return j
   506  }
   507  
   508  func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
   509  	tg.Name = *taskGroup.Name
   510  	tg.Count = *taskGroup.Count
   511  	tg.Meta = taskGroup.Meta
   512  
   513  	if l := len(taskGroup.Constraints); l != 0 {
   514  		tg.Constraints = make([]*structs.Constraint, l)
   515  		for k, constraint := range taskGroup.Constraints {
   516  			c := &structs.Constraint{}
   517  			ApiConstraintToStructs(constraint, c)
   518  			tg.Constraints[k] = c
   519  		}
   520  	}
   521  
   522  	tg.RestartPolicy = &structs.RestartPolicy{
   523  		Attempts: *taskGroup.RestartPolicy.Attempts,
   524  		Interval: *taskGroup.RestartPolicy.Interval,
   525  		Delay:    *taskGroup.RestartPolicy.Delay,
   526  		Mode:     *taskGroup.RestartPolicy.Mode,
   527  	}
   528  
   529  	tg.EphemeralDisk = &structs.EphemeralDisk{
   530  		Sticky:  *taskGroup.EphemeralDisk.Sticky,
   531  		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
   532  		Migrate: *taskGroup.EphemeralDisk.Migrate,
   533  	}
   534  
   535  	if l := len(taskGroup.Tasks); l != 0 {
   536  		tg.Tasks = make([]*structs.Task, l)
   537  		for l, task := range taskGroup.Tasks {
   538  			t := &structs.Task{}
   539  			ApiTaskToStructsTask(task, t)
   540  			tg.Tasks[l] = t
   541  		}
   542  	}
   543  }
   544  
   545  func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
   546  	structsTask.Name = apiTask.Name
   547  	structsTask.Driver = apiTask.Driver
   548  	structsTask.User = apiTask.User
   549  	structsTask.Leader = apiTask.Leader
   550  	structsTask.Config = apiTask.Config
   551  	structsTask.Env = apiTask.Env
   552  	structsTask.Meta = apiTask.Meta
   553  	structsTask.KillTimeout = *apiTask.KillTimeout
   554  
   555  	if l := len(apiTask.Constraints); l != 0 {
   556  		structsTask.Constraints = make([]*structs.Constraint, l)
   557  		for i, constraint := range apiTask.Constraints {
   558  			c := &structs.Constraint{}
   559  			ApiConstraintToStructs(constraint, c)
   560  			structsTask.Constraints[i] = c
   561  		}
   562  	}
   563  
   564  	if l := len(apiTask.Services); l != 0 {
   565  		structsTask.Services = make([]*structs.Service, l)
   566  		for i, service := range apiTask.Services {
   567  			structsTask.Services[i] = &structs.Service{
   568  				Name:      service.Name,
   569  				PortLabel: service.PortLabel,
   570  				Tags:      service.Tags,
   571  			}
   572  
   573  			if l := len(service.Checks); l != 0 {
   574  				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
   575  				for j, check := range service.Checks {
   576  					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
   577  						Name:          check.Name,
   578  						Type:          check.Type,
   579  						Command:       check.Command,
   580  						Args:          check.Args,
   581  						Path:          check.Path,
   582  						Protocol:      check.Protocol,
   583  						PortLabel:     check.PortLabel,
   584  						Interval:      check.Interval,
   585  						Timeout:       check.Timeout,
   586  						InitialStatus: check.InitialStatus,
   587  						TLSSkipVerify: check.TLSSkipVerify,
   588  					}
   589  				}
   590  			}
   591  		}
   592  	}
   593  
   594  	structsTask.Resources = &structs.Resources{
   595  		CPU:      *apiTask.Resources.CPU,
   596  		MemoryMB: *apiTask.Resources.MemoryMB,
   597  		IOPS:     *apiTask.Resources.IOPS,
   598  	}
   599  
   600  	if l := len(apiTask.Resources.Networks); l != 0 {
   601  		structsTask.Resources.Networks = make([]*structs.NetworkResource, l)
   602  		for i, nw := range apiTask.Resources.Networks {
   603  			structsTask.Resources.Networks[i] = &structs.NetworkResource{
   604  				CIDR:  nw.CIDR,
   605  				IP:    nw.IP,
   606  				MBits: *nw.MBits,
   607  			}
   608  
   609  			if l := len(nw.DynamicPorts); l != 0 {
   610  				structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, l)
   611  				for j, dp := range nw.DynamicPorts {
   612  					structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{
   613  						Label: dp.Label,
   614  						Value: dp.Value,
   615  					}
   616  				}
   617  			}
   618  
   619  			if l := len(nw.ReservedPorts); l != 0 {
   620  				structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, l)
   621  				for j, rp := range nw.ReservedPorts {
   622  					structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{
   623  						Label: rp.Label,
   624  						Value: rp.Value,
   625  					}
   626  				}
   627  			}
   628  		}
   629  	}
   630  
   631  	structsTask.LogConfig = &structs.LogConfig{
   632  		MaxFiles:      *apiTask.LogConfig.MaxFiles,
   633  		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
   634  	}
   635  
   636  	if l := len(apiTask.Artifacts); l != 0 {
   637  		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
   638  		for k, ta := range apiTask.Artifacts {
   639  			structsTask.Artifacts[k] = &structs.TaskArtifact{
   640  				GetterSource:  *ta.GetterSource,
   641  				GetterOptions: ta.GetterOptions,
   642  				RelativeDest:  *ta.RelativeDest,
   643  			}
   644  		}
   645  	}
   646  
   647  	if apiTask.Vault != nil {
   648  		structsTask.Vault = &structs.Vault{
   649  			Policies:     apiTask.Vault.Policies,
   650  			Env:          *apiTask.Vault.Env,
   651  			ChangeMode:   *apiTask.Vault.ChangeMode,
   652  			ChangeSignal: *apiTask.Vault.ChangeSignal,
   653  		}
   654  	}
   655  
   656  	if l := len(apiTask.Templates); l != 0 {
   657  		structsTask.Templates = make([]*structs.Template, l)
   658  		for i, template := range apiTask.Templates {
   659  			structsTask.Templates[i] = &structs.Template{
   660  				SourcePath:   *template.SourcePath,
   661  				DestPath:     *template.DestPath,
   662  				EmbeddedTmpl: *template.EmbeddedTmpl,
   663  				ChangeMode:   *template.ChangeMode,
   664  				ChangeSignal: *template.ChangeSignal,
   665  				Splay:        *template.Splay,
   666  				Perms:        *template.Perms,
   667  				LeftDelim:    *template.LeftDelim,
   668  				RightDelim:   *template.RightDelim,
   669  			}
   670  		}
   671  	}
   672  
   673  	if apiTask.DispatchPayload != nil {
   674  		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
   675  			File: apiTask.DispatchPayload.File,
   676  		}
   677  	}
   678  }
   679  
   680  func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) {
   681  	c2.LTarget = c1.LTarget
   682  	c2.RTarget = c1.RTarget
   683  	c2.Operand = c1.Operand
   684  }