1package agent
2
3import (
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/helper"
12	"github.com/hashicorp/nomad/jobspec"
13	"github.com/hashicorp/nomad/nomad/structs"
14)
15
16func (s *HTTPServer) JobsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
17	switch req.Method {
18	case "GET":
19		return s.jobListRequest(resp, req)
20	case "PUT", "POST":
21		return s.jobUpdate(resp, req, "")
22	default:
23		return nil, CodedError(405, ErrInvalidMethod)
24	}
25}
26
27func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
28	args := structs.JobListRequest{}
29	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
30		return nil, nil
31	}
32
33	var out structs.JobListResponse
34	if err := s.agent.RPC("Job.List", &args, &out); err != nil {
35		return nil, err
36	}
37
38	setMeta(resp, &out.QueryMeta)
39	if out.Jobs == nil {
40		out.Jobs = make([]*structs.JobListStub, 0)
41	}
42	return out.Jobs, nil
43}
44
45func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
46	path := strings.TrimPrefix(req.URL.Path, "/v1/job/")
47	switch {
48	case strings.HasSuffix(path, "/evaluate"):
49		jobName := strings.TrimSuffix(path, "/evaluate")
50		return s.jobForceEvaluate(resp, req, jobName)
51	case strings.HasSuffix(path, "/allocations"):
52		jobName := strings.TrimSuffix(path, "/allocations")
53		return s.jobAllocations(resp, req, jobName)
54	case strings.HasSuffix(path, "/evaluations"):
55		jobName := strings.TrimSuffix(path, "/evaluations")
56		return s.jobEvaluations(resp, req, jobName)
57	case strings.HasSuffix(path, "/periodic/force"):
58		jobName := strings.TrimSuffix(path, "/periodic/force")
59		return s.periodicForceRequest(resp, req, jobName)
60	case strings.HasSuffix(path, "/plan"):
61		jobName := strings.TrimSuffix(path, "/plan")
62		return s.jobPlan(resp, req, jobName)
63	case strings.HasSuffix(path, "/summary"):
64		jobName := strings.TrimSuffix(path, "/summary")
65		return s.jobSummaryRequest(resp, req, jobName)
66	case strings.HasSuffix(path, "/dispatch"):
67		jobName := strings.TrimSuffix(path, "/dispatch")
68		return s.jobDispatchRequest(resp, req, jobName)
69	case strings.HasSuffix(path, "/versions"):
70		jobName := strings.TrimSuffix(path, "/versions")
71		return s.jobVersions(resp, req, jobName)
72	case strings.HasSuffix(path, "/revert"):
73		jobName := strings.TrimSuffix(path, "/revert")
74		return s.jobRevert(resp, req, jobName)
75	case strings.HasSuffix(path, "/deployments"):
76		jobName := strings.TrimSuffix(path, "/deployments")
77		return s.jobDeployments(resp, req, jobName)
78	case strings.HasSuffix(path, "/deployment"):
79		jobName := strings.TrimSuffix(path, "/deployment")
80		return s.jobLatestDeployment(resp, req, jobName)
81	case strings.HasSuffix(path, "/stable"):
82		jobName := strings.TrimSuffix(path, "/stable")
83		return s.jobStable(resp, req, jobName)
84	default:
85		return s.jobCRUD(resp, req, path)
86	}
87}
88
89func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Request,
90	jobName string) (interface{}, error) {
91	if req.Method != "PUT" && req.Method != "POST" {
92		return nil, CodedError(405, ErrInvalidMethod)
93	}
94	var args structs.JobEvaluateRequest
95
96	// TODO(preetha): remove in 0.9
97	// COMPAT: For backwards compatibility allow using this endpoint without a payload
98	if req.ContentLength == 0 {
99		args = structs.JobEvaluateRequest{
100			JobID: jobName,
101		}
102	} else {
103		if err := decodeBody(req, &args); err != nil {
104			return nil, CodedError(400, err.Error())
105		}
106		if args.JobID == "" {
107			return nil, CodedError(400, "Job ID must be specified")
108		}
109
110		if jobName != "" && args.JobID != jobName {
111			return nil, CodedError(400, "JobID not same as job name")
112		}
113	}
114	s.parseWriteRequest(req, &args.WriteRequest)
115
116	var out structs.JobRegisterResponse
117	if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil {
118		return nil, err
119	}
120	setIndex(resp, out.Index)
121	return out, nil
122}
123
124func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request,
125	jobName string) (interface{}, error) {
126	if req.Method != "PUT" && req.Method != "POST" {
127		return nil, CodedError(405, ErrInvalidMethod)
128	}
129
130	var args api.JobPlanRequest
131	if err := decodeBody(req, &args); err != nil {
132		return nil, CodedError(400, err.Error())
133	}
134	if args.Job == nil {
135		return nil, CodedError(400, "Job must be specified")
136	}
137	if args.Job.ID == nil {
138		return nil, CodedError(400, "Job must have a valid ID")
139	}
140	if jobName != "" && *args.Job.ID != jobName {
141		return nil, CodedError(400, "Job ID does not match")
142	}
143
144	// Region in http request query param takes precedence over region in job hcl config
145	if args.WriteRequest.Region != "" {
146		args.Job.Region = helper.StringToPtr(args.WriteRequest.Region)
147	}
148	// If 'global' region is specified or if no region is given,
149	// default to region of the node you're submitting to
150	if args.Job.Region == nil || *args.Job.Region == "" || *args.Job.Region == api.GlobalRegion {
151		args.Job.Region = &s.agent.config.Region
152	}
153
154	sJob := ApiJobToStructJob(args.Job)
155
156	planReq := structs.JobPlanRequest{
157		Job:            sJob,
158		Diff:           args.Diff,
159		PolicyOverride: args.PolicyOverride,
160		WriteRequest: structs.WriteRequest{
161			Region: sJob.Region,
162		},
163	}
164	// parseWriteRequest overrides Namespace, Region and AuthToken
165	// based on values from the original http request
166	s.parseWriteRequest(req, &planReq.WriteRequest)
167	planReq.Namespace = sJob.Namespace
168
169	var out structs.JobPlanResponse
170	if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil {
171		return nil, err
172	}
173	setIndex(resp, out.Index)
174	return out, nil
175}
176
177func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
178	// Ensure request method is POST or PUT
179	if !(req.Method == "POST" || req.Method == "PUT") {
180		return nil, CodedError(405, ErrInvalidMethod)
181	}
182
183	var validateRequest api.JobValidateRequest
184	if err := decodeBody(req, &validateRequest); err != nil {
185		return nil, CodedError(400, err.Error())
186	}
187	if validateRequest.Job == nil {
188		return nil, CodedError(400, "Job must be specified")
189	}
190
191	job := ApiJobToStructJob(validateRequest.Job)
192
193	args := structs.JobValidateRequest{
194		Job: job,
195		WriteRequest: structs.WriteRequest{
196			Region: validateRequest.Region,
197		},
198	}
199	s.parseWriteRequest(req, &args.WriteRequest)
200	args.Namespace = job.Namespace
201
202	var out structs.JobValidateResponse
203	if err := s.agent.RPC("Job.Validate", &args, &out); err != nil {
204		return nil, err
205	}
206
207	return out, nil
208}
209
210func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request,
211	jobName string) (interface{}, error) {
212	if req.Method != "PUT" && req.Method != "POST" {
213		return nil, CodedError(405, ErrInvalidMethod)
214	}
215
216	args := structs.PeriodicForceRequest{
217		JobID: jobName,
218	}
219	s.parseWriteRequest(req, &args.WriteRequest)
220
221	var out structs.PeriodicForceResponse
222	if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil {
223		return nil, err
224	}
225	setIndex(resp, out.Index)
226	return out, nil
227}
228
229func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request,
230	jobName string) (interface{}, error) {
231	if req.Method != "GET" {
232		return nil, CodedError(405, ErrInvalidMethod)
233	}
234	allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all"))
235
236	args := structs.JobSpecificRequest{
237		JobID: jobName,
238		All:   allAllocs,
239	}
240	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
241		return nil, nil
242	}
243
244	var out structs.JobAllocationsResponse
245	if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil {
246		return nil, err
247	}
248
249	setMeta(resp, &out.QueryMeta)
250	if out.Allocations == nil {
251		out.Allocations = make([]*structs.AllocListStub, 0)
252	}
253	for _, alloc := range out.Allocations {
254		alloc.SetEventDisplayMessages()
255	}
256	return out.Allocations, nil
257}
258
259func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request,
260	jobName string) (interface{}, error) {
261	if req.Method != "GET" {
262		return nil, CodedError(405, ErrInvalidMethod)
263	}
264	args := structs.JobSpecificRequest{
265		JobID: jobName,
266	}
267	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
268		return nil, nil
269	}
270
271	var out structs.JobEvaluationsResponse
272	if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil {
273		return nil, err
274	}
275
276	setMeta(resp, &out.QueryMeta)
277	if out.Evaluations == nil {
278		out.Evaluations = make([]*structs.Evaluation, 0)
279	}
280	return out.Evaluations, nil
281}
282
283func (s *HTTPServer) jobDeployments(resp http.ResponseWriter, req *http.Request,
284	jobName string) (interface{}, error) {
285	if req.Method != "GET" {
286		return nil, CodedError(405, ErrInvalidMethod)
287	}
288	all, _ := strconv.ParseBool(req.URL.Query().Get("all"))
289	args := structs.JobSpecificRequest{
290		JobID: jobName,
291		All:   all,
292	}
293	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
294		return nil, nil
295	}
296
297	var out structs.DeploymentListResponse
298	if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil {
299		return nil, err
300	}
301
302	setMeta(resp, &out.QueryMeta)
303	if out.Deployments == nil {
304		out.Deployments = make([]*structs.Deployment, 0)
305	}
306	return out.Deployments, nil
307}
308
309func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request,
310	jobName string) (interface{}, error) {
311	if req.Method != "GET" {
312		return nil, CodedError(405, ErrInvalidMethod)
313	}
314	args := structs.JobSpecificRequest{
315		JobID: jobName,
316	}
317	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
318		return nil, nil
319	}
320
321	var out structs.SingleDeploymentResponse
322	if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil {
323		return nil, err
324	}
325
326	setMeta(resp, &out.QueryMeta)
327	return out.Deployment, nil
328}
329
330func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request,
331	jobName string) (interface{}, error) {
332	switch req.Method {
333	case "GET":
334		return s.jobQuery(resp, req, jobName)
335	case "PUT", "POST":
336		return s.jobUpdate(resp, req, jobName)
337	case "DELETE":
338		return s.jobDelete(resp, req, jobName)
339	default:
340		return nil, CodedError(405, ErrInvalidMethod)
341	}
342}
343
344func (s *HTTPServer) jobQuery(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.SingleJobResponse
354	if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil {
355		return nil, err
356	}
357
358	setMeta(resp, &out.QueryMeta)
359	if out.Job == nil {
360		return nil, CodedError(404, "job not found")
361	}
362
363	// Decode the payload if there is any
364	job := out.Job
365	if len(job.Payload) != 0 {
366		decoded, err := snappy.Decode(nil, out.Job.Payload)
367		if err != nil {
368			return nil, err
369		}
370		job = job.Copy()
371		job.Payload = decoded
372	}
373
374	return job, nil
375}
376
377func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request,
378	jobName string) (interface{}, error) {
379	var args api.JobRegisterRequest
380	if err := decodeBody(req, &args); err != nil {
381		return nil, CodedError(400, err.Error())
382	}
383	if args.Job == nil {
384		return nil, CodedError(400, "Job must be specified")
385	}
386
387	if args.Job.ID == nil {
388		return nil, CodedError(400, "Job ID hasn't been provided")
389	}
390	if jobName != "" && *args.Job.ID != jobName {
391		return nil, CodedError(400, "Job ID does not match name")
392	}
393
394	// Region in http request query param takes precedence over region in job hcl config
395	if args.WriteRequest.Region != "" {
396		args.Job.Region = helper.StringToPtr(args.WriteRequest.Region)
397	}
398	// If 'global' region is specified or if no region is given,
399	// default to region of the node you're submitting to
400	if args.Job.Region == nil || *args.Job.Region == "" || *args.Job.Region == api.GlobalRegion {
401		args.Job.Region = &s.agent.config.Region
402	}
403
404	sJob := ApiJobToStructJob(args.Job)
405
406	regReq := structs.JobRegisterRequest{
407		Job:            sJob,
408		EnforceIndex:   args.EnforceIndex,
409		JobModifyIndex: args.JobModifyIndex,
410		PolicyOverride: args.PolicyOverride,
411		WriteRequest: structs.WriteRequest{
412			Region:    sJob.Region,
413			AuthToken: args.WriteRequest.SecretID,
414		},
415	}
416	// parseWriteRequest overrides Namespace, Region and AuthToken
417	// based on values from the original http request
418	s.parseWriteRequest(req, &regReq.WriteRequest)
419	regReq.Namespace = sJob.Namespace
420
421	var out structs.JobRegisterResponse
422	if err := s.agent.RPC("Job.Register", &regReq, &out); err != nil {
423		return nil, err
424	}
425	setIndex(resp, out.Index)
426	return out, nil
427}
428
429func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request,
430	jobName string) (interface{}, error) {
431
432	purgeStr := req.URL.Query().Get("purge")
433	var purgeBool bool
434	if purgeStr != "" {
435		var err error
436		purgeBool, err = strconv.ParseBool(purgeStr)
437		if err != nil {
438			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err)
439		}
440	}
441
442	args := structs.JobDeregisterRequest{
443		JobID: jobName,
444		Purge: purgeBool,
445	}
446	s.parseWriteRequest(req, &args.WriteRequest)
447
448	var out structs.JobDeregisterResponse
449	if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil {
450		return nil, err
451	}
452	setIndex(resp, out.Index)
453	return out, nil
454}
455
456func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request,
457	jobName string) (interface{}, error) {
458
459	diffsStr := req.URL.Query().Get("diffs")
460	var diffsBool bool
461	if diffsStr != "" {
462		var err error
463		diffsBool, err = strconv.ParseBool(diffsStr)
464		if err != nil {
465			return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err)
466		}
467	}
468
469	args := structs.JobVersionsRequest{
470		JobID: jobName,
471		Diffs: diffsBool,
472	}
473	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
474		return nil, nil
475	}
476
477	var out structs.JobVersionsResponse
478	if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil {
479		return nil, err
480	}
481
482	setMeta(resp, &out.QueryMeta)
483	if len(out.Versions) == 0 {
484		return nil, CodedError(404, "job versions not found")
485	}
486
487	return out, nil
488}
489
490func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request,
491	jobName string) (interface{}, error) {
492
493	if req.Method != "PUT" && req.Method != "POST" {
494		return nil, CodedError(405, ErrInvalidMethod)
495	}
496
497	var revertRequest structs.JobRevertRequest
498	if err := decodeBody(req, &revertRequest); err != nil {
499		return nil, CodedError(400, err.Error())
500	}
501	if revertRequest.JobID == "" {
502		return nil, CodedError(400, "JobID must be specified")
503	}
504	if revertRequest.JobID != jobName {
505		return nil, CodedError(400, "Job ID does not match")
506	}
507
508	s.parseWriteRequest(req, &revertRequest.WriteRequest)
509
510	var out structs.JobRegisterResponse
511	if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil {
512		return nil, err
513	}
514
515	setMeta(resp, &out.QueryMeta)
516	return out, nil
517}
518
519func (s *HTTPServer) jobStable(resp http.ResponseWriter, req *http.Request,
520	jobName string) (interface{}, error) {
521
522	if req.Method != "PUT" && req.Method != "POST" {
523		return nil, CodedError(405, ErrInvalidMethod)
524	}
525
526	var stableRequest structs.JobStabilityRequest
527	if err := decodeBody(req, &stableRequest); err != nil {
528		return nil, CodedError(400, err.Error())
529	}
530	if stableRequest.JobID == "" {
531		return nil, CodedError(400, "JobID must be specified")
532	}
533	if stableRequest.JobID != jobName {
534		return nil, CodedError(400, "Job ID does not match")
535	}
536
537	s.parseWriteRequest(req, &stableRequest.WriteRequest)
538
539	var out structs.JobStabilityResponse
540	if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil {
541		return nil, err
542	}
543
544	setIndex(resp, out.Index)
545	return out, nil
546}
547
548func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
549	args := structs.JobSummaryRequest{
550		JobID: name,
551	}
552	if s.parse(resp, req, &args.Region, &args.QueryOptions) {
553		return nil, nil
554	}
555
556	var out structs.JobSummaryResponse
557	if err := s.agent.RPC("Job.Summary", &args, &out); err != nil {
558		return nil, err
559	}
560
561	setMeta(resp, &out.QueryMeta)
562	if out.JobSummary == nil {
563		return nil, CodedError(404, "job not found")
564	}
565	setIndex(resp, out.Index)
566	return out.JobSummary, nil
567}
568
569func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) {
570	if req.Method != "PUT" && req.Method != "POST" {
571		return nil, CodedError(405, ErrInvalidMethod)
572	}
573	args := structs.JobDispatchRequest{}
574	if err := decodeBody(req, &args); err != nil {
575		return nil, CodedError(400, err.Error())
576	}
577	if args.JobID != "" && args.JobID != name {
578		return nil, CodedError(400, "Job ID does not match")
579	}
580	if args.JobID == "" {
581		args.JobID = name
582	}
583
584	s.parseWriteRequest(req, &args.WriteRequest)
585
586	var out structs.JobDispatchResponse
587	if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil {
588		return nil, err
589	}
590	setIndex(resp, out.Index)
591	return out, nil
592}
593
594// JobsParseRequest parses a hcl jobspec and returns a api.Job
595func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
596	if req.Method != http.MethodPut && req.Method != http.MethodPost {
597		return nil, CodedError(405, ErrInvalidMethod)
598	}
599
600	args := &api.JobsParseRequest{}
601	if err := decodeBody(req, &args); err != nil {
602		return nil, CodedError(400, err.Error())
603	}
604	if args.JobHCL == "" {
605		return nil, CodedError(400, "Job spec is empty")
606	}
607
608	jobfile := strings.NewReader(args.JobHCL)
609	jobStruct, err := jobspec.Parse(jobfile)
610	if err != nil {
611		return nil, CodedError(400, err.Error())
612	}
613
614	if args.Canonicalize {
615		jobStruct.Canonicalize()
616	}
617	return jobStruct, nil
618}
619
620func ApiJobToStructJob(job *api.Job) *structs.Job {
621	job.Canonicalize()
622
623	j := &structs.Job{
624		Stop:        *job.Stop,
625		Region:      *job.Region,
626		Namespace:   *job.Namespace,
627		ID:          *job.ID,
628		ParentID:    *job.ParentID,
629		Name:        *job.Name,
630		Type:        *job.Type,
631		Priority:    *job.Priority,
632		AllAtOnce:   *job.AllAtOnce,
633		Datacenters: job.Datacenters,
634		Payload:     job.Payload,
635		Meta:        job.Meta,
636		VaultToken:  *job.VaultToken,
637		Constraints: ApiConstraintsToStructs(job.Constraints),
638		Affinities:  ApiAffinitiesToStructs(job.Affinities),
639	}
640
641	// Update has been pushed into the task groups. stagger and max_parallel are
642	// preserved at the job level, but all other values are discarded. The job.Update
643	// api value is merged into TaskGroups already in api.Canonicalize
644	if job.Update != nil && job.Update.MaxParallel != nil && *job.Update.MaxParallel > 0 {
645		j.Update = structs.UpdateStrategy{}
646
647		if job.Update.Stagger != nil {
648			j.Update.Stagger = *job.Update.Stagger
649		}
650		if job.Update.MaxParallel != nil {
651			j.Update.MaxParallel = *job.Update.MaxParallel
652		}
653	}
654
655	if l := len(job.Spreads); l != 0 {
656		j.Spreads = make([]*structs.Spread, l)
657		for i, apiSpread := range job.Spreads {
658			j.Spreads[i] = ApiSpreadToStructs(apiSpread)
659		}
660	}
661
662	if job.Periodic != nil {
663		j.Periodic = &structs.PeriodicConfig{
664			Enabled:         *job.Periodic.Enabled,
665			SpecType:        *job.Periodic.SpecType,
666			ProhibitOverlap: *job.Periodic.ProhibitOverlap,
667			TimeZone:        *job.Periodic.TimeZone,
668		}
669
670		if job.Periodic.Spec != nil {
671			j.Periodic.Spec = *job.Periodic.Spec
672		}
673	}
674
675	if job.ParameterizedJob != nil {
676		j.ParameterizedJob = &structs.ParameterizedJobConfig{
677			Payload:      job.ParameterizedJob.Payload,
678			MetaRequired: job.ParameterizedJob.MetaRequired,
679			MetaOptional: job.ParameterizedJob.MetaOptional,
680		}
681	}
682
683	if l := len(job.TaskGroups); l != 0 {
684		j.TaskGroups = make([]*structs.TaskGroup, l)
685		for i, taskGroup := range job.TaskGroups {
686			tg := &structs.TaskGroup{}
687			ApiTgToStructsTG(taskGroup, tg)
688			j.TaskGroups[i] = tg
689		}
690	}
691
692	return j
693}
694
695func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) {
696	tg.Name = *taskGroup.Name
697	tg.Count = *taskGroup.Count
698	tg.Meta = taskGroup.Meta
699	tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints)
700	tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities)
701	tg.Networks = ApiNetworkResourceToStructs(taskGroup.Networks)
702	tg.Services = ApiServicesToStructs(taskGroup.Services)
703
704	tg.RestartPolicy = &structs.RestartPolicy{
705		Attempts: *taskGroup.RestartPolicy.Attempts,
706		Interval: *taskGroup.RestartPolicy.Interval,
707		Delay:    *taskGroup.RestartPolicy.Delay,
708		Mode:     *taskGroup.RestartPolicy.Mode,
709	}
710
711	if taskGroup.ShutdownDelay != nil {
712		tg.ShutdownDelay = taskGroup.ShutdownDelay
713	}
714
715	if taskGroup.ReschedulePolicy != nil {
716		tg.ReschedulePolicy = &structs.ReschedulePolicy{
717			Attempts:      *taskGroup.ReschedulePolicy.Attempts,
718			Interval:      *taskGroup.ReschedulePolicy.Interval,
719			Delay:         *taskGroup.ReschedulePolicy.Delay,
720			DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction,
721			MaxDelay:      *taskGroup.ReschedulePolicy.MaxDelay,
722			Unlimited:     *taskGroup.ReschedulePolicy.Unlimited,
723		}
724	}
725
726	if taskGroup.Migrate != nil {
727		tg.Migrate = &structs.MigrateStrategy{
728			MaxParallel:     *taskGroup.Migrate.MaxParallel,
729			HealthCheck:     *taskGroup.Migrate.HealthCheck,
730			MinHealthyTime:  *taskGroup.Migrate.MinHealthyTime,
731			HealthyDeadline: *taskGroup.Migrate.HealthyDeadline,
732		}
733	}
734
735	tg.EphemeralDisk = &structs.EphemeralDisk{
736		Sticky:  *taskGroup.EphemeralDisk.Sticky,
737		SizeMB:  *taskGroup.EphemeralDisk.SizeMB,
738		Migrate: *taskGroup.EphemeralDisk.Migrate,
739	}
740
741	if l := len(taskGroup.Spreads); l != 0 {
742		tg.Spreads = make([]*structs.Spread, l)
743		for k, spread := range taskGroup.Spreads {
744			tg.Spreads[k] = ApiSpreadToStructs(spread)
745		}
746	}
747
748	if l := len(taskGroup.Volumes); l != 0 {
749		tg.Volumes = make(map[string]*structs.VolumeRequest, l)
750		for k, v := range taskGroup.Volumes {
751			if v.Type != structs.VolumeTypeHost {
752				// Ignore non-host volumes in this iteration currently.
753				continue
754			}
755
756			vol := &structs.VolumeRequest{
757				Name:     v.Name,
758				Type:     v.Type,
759				ReadOnly: v.ReadOnly,
760				Source:   v.Source,
761			}
762
763			tg.Volumes[k] = vol
764		}
765	}
766
767	if taskGroup.Update != nil {
768		tg.Update = &structs.UpdateStrategy{
769			Stagger:          *taskGroup.Update.Stagger,
770			MaxParallel:      *taskGroup.Update.MaxParallel,
771			HealthCheck:      *taskGroup.Update.HealthCheck,
772			MinHealthyTime:   *taskGroup.Update.MinHealthyTime,
773			HealthyDeadline:  *taskGroup.Update.HealthyDeadline,
774			ProgressDeadline: *taskGroup.Update.ProgressDeadline,
775			Canary:           *taskGroup.Update.Canary,
776		}
777
778		// boolPtr fields may be nil, others will have pointers to default values via Canonicalize
779		if taskGroup.Update.AutoRevert != nil {
780			tg.Update.AutoRevert = *taskGroup.Update.AutoRevert
781		}
782
783		if taskGroup.Update.AutoPromote != nil {
784			tg.Update.AutoPromote = *taskGroup.Update.AutoPromote
785		}
786	}
787
788	if l := len(taskGroup.Tasks); l != 0 {
789		tg.Tasks = make([]*structs.Task, l)
790		for l, task := range taskGroup.Tasks {
791			t := &structs.Task{}
792			ApiTaskToStructsTask(task, t)
793			tg.Tasks[l] = t
794		}
795	}
796}
797
798// ApiTaskToStructsTask is a copy and type conversion between the API
799// representation of a task from a struct representation of a task.
800func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) {
801	structsTask.Name = apiTask.Name
802	structsTask.Driver = apiTask.Driver
803	structsTask.User = apiTask.User
804	structsTask.Leader = apiTask.Leader
805	structsTask.Config = apiTask.Config
806	structsTask.Env = apiTask.Env
807	structsTask.Meta = apiTask.Meta
808	structsTask.KillTimeout = *apiTask.KillTimeout
809	structsTask.ShutdownDelay = apiTask.ShutdownDelay
810	structsTask.KillSignal = apiTask.KillSignal
811	structsTask.Kind = structs.TaskKind(apiTask.Kind)
812	structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints)
813	structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities)
814
815	if l := len(apiTask.VolumeMounts); l != 0 {
816		structsTask.VolumeMounts = make([]*structs.VolumeMount, l)
817		for i, mount := range apiTask.VolumeMounts {
818			structsTask.VolumeMounts[i] = &structs.VolumeMount{
819				Volume:          *mount.Volume,
820				Destination:     *mount.Destination,
821				ReadOnly:        *mount.ReadOnly,
822				PropagationMode: *mount.PropagationMode,
823			}
824		}
825	}
826
827	if l := len(apiTask.Services); l != 0 {
828		structsTask.Services = make([]*structs.Service, l)
829		for i, service := range apiTask.Services {
830			structsTask.Services[i] = &structs.Service{
831				Name:        service.Name,
832				PortLabel:   service.PortLabel,
833				Tags:        service.Tags,
834				CanaryTags:  service.CanaryTags,
835				AddressMode: service.AddressMode,
836				Meta:        helper.CopyMapStringString(service.Meta),
837			}
838
839			if l := len(service.Checks); l != 0 {
840				structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l)
841				for j, check := range service.Checks {
842					structsTask.Services[i].Checks[j] = &structs.ServiceCheck{
843						Name:          check.Name,
844						Type:          check.Type,
845						Command:       check.Command,
846						Args:          check.Args,
847						Path:          check.Path,
848						Protocol:      check.Protocol,
849						PortLabel:     check.PortLabel,
850						AddressMode:   check.AddressMode,
851						Interval:      check.Interval,
852						Timeout:       check.Timeout,
853						InitialStatus: check.InitialStatus,
854						TLSSkipVerify: check.TLSSkipVerify,
855						Header:        check.Header,
856						Method:        check.Method,
857						GRPCService:   check.GRPCService,
858						GRPCUseTLS:    check.GRPCUseTLS,
859					}
860					if check.CheckRestart != nil {
861						structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{
862							Limit:          check.CheckRestart.Limit,
863							Grace:          *check.CheckRestart.Grace,
864							IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
865						}
866					}
867				}
868			}
869		}
870	}
871
872	structsTask.Resources = ApiResourcesToStructs(apiTask.Resources)
873
874	structsTask.LogConfig = &structs.LogConfig{
875		MaxFiles:      *apiTask.LogConfig.MaxFiles,
876		MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB,
877	}
878
879	if l := len(apiTask.Artifacts); l != 0 {
880		structsTask.Artifacts = make([]*structs.TaskArtifact, l)
881		for k, ta := range apiTask.Artifacts {
882			structsTask.Artifacts[k] = &structs.TaskArtifact{
883				GetterSource:  *ta.GetterSource,
884				GetterOptions: ta.GetterOptions,
885				GetterMode:    *ta.GetterMode,
886				RelativeDest:  *ta.RelativeDest,
887			}
888		}
889	}
890
891	if apiTask.Vault != nil {
892		structsTask.Vault = &structs.Vault{
893			Policies:     apiTask.Vault.Policies,
894			Env:          *apiTask.Vault.Env,
895			ChangeMode:   *apiTask.Vault.ChangeMode,
896			ChangeSignal: *apiTask.Vault.ChangeSignal,
897		}
898	}
899
900	if l := len(apiTask.Templates); l != 0 {
901		structsTask.Templates = make([]*structs.Template, l)
902		for i, template := range apiTask.Templates {
903			structsTask.Templates[i] = &structs.Template{
904				SourcePath:   *template.SourcePath,
905				DestPath:     *template.DestPath,
906				EmbeddedTmpl: *template.EmbeddedTmpl,
907				ChangeMode:   *template.ChangeMode,
908				ChangeSignal: *template.ChangeSignal,
909				Splay:        *template.Splay,
910				Perms:        *template.Perms,
911				LeftDelim:    *template.LeftDelim,
912				RightDelim:   *template.RightDelim,
913				Envvars:      *template.Envvars,
914				VaultGrace:   *template.VaultGrace,
915			}
916		}
917	}
918
919	if apiTask.DispatchPayload != nil {
920		structsTask.DispatchPayload = &structs.DispatchPayloadConfig{
921			File: apiTask.DispatchPayload.File,
922		}
923	}
924}
925
926func ApiResourcesToStructs(in *api.Resources) *structs.Resources {
927	if in == nil {
928		return nil
929	}
930
931	out := &structs.Resources{
932		CPU:      *in.CPU,
933		MemoryMB: *in.MemoryMB,
934	}
935
936	// COMPAT(0.10): Only being used to issue warnings
937	if in.IOPS != nil {
938		out.IOPS = *in.IOPS
939	}
940
941	if len(in.Networks) != 0 {
942		out.Networks = ApiNetworkResourceToStructs(in.Networks)
943	}
944
945	if l := len(in.Devices); l != 0 {
946		out.Devices = make([]*structs.RequestedDevice, l)
947		for i, d := range in.Devices {
948			out.Devices[i] = &structs.RequestedDevice{
949				Name:        d.Name,
950				Count:       *d.Count,
951				Constraints: ApiConstraintsToStructs(d.Constraints),
952				Affinities:  ApiAffinitiesToStructs(d.Affinities),
953			}
954		}
955	}
956
957	return out
958}
959
960func ApiNetworkResourceToStructs(in []*api.NetworkResource) []*structs.NetworkResource {
961	var out []*structs.NetworkResource
962	if len(in) == 0 {
963		return out
964	}
965	out = make([]*structs.NetworkResource, len(in))
966	for i, nw := range in {
967		out[i] = &structs.NetworkResource{
968			Mode:  nw.Mode,
969			CIDR:  nw.CIDR,
970			IP:    nw.IP,
971			MBits: *nw.MBits,
972		}
973
974		if l := len(nw.DynamicPorts); l != 0 {
975			out[i].DynamicPorts = make([]structs.Port, l)
976			for j, dp := range nw.DynamicPorts {
977				out[i].DynamicPorts[j] = structs.Port{
978					Label: dp.Label,
979					Value: dp.Value,
980					To:    dp.To,
981				}
982			}
983		}
984
985		if l := len(nw.ReservedPorts); l != 0 {
986			out[i].ReservedPorts = make([]structs.Port, l)
987			for j, rp := range nw.ReservedPorts {
988				out[i].ReservedPorts[j] = structs.Port{
989					Label: rp.Label,
990					Value: rp.Value,
991					To:    rp.To,
992				}
993			}
994		}
995	}
996
997	return out
998}
999
1000//TODO(schmichael) refactor and reuse in service parsing above
1001func ApiServicesToStructs(in []*api.Service) []*structs.Service {
1002	if len(in) == 0 {
1003		return nil
1004	}
1005
1006	out := make([]*structs.Service, len(in))
1007	for i, s := range in {
1008		out[i] = &structs.Service{
1009			Name:        s.Name,
1010			PortLabel:   s.PortLabel,
1011			Tags:        s.Tags,
1012			CanaryTags:  s.CanaryTags,
1013			AddressMode: s.AddressMode,
1014			Meta:        helper.CopyMapStringString(s.Meta),
1015		}
1016
1017		if l := len(s.Checks); l != 0 {
1018			out[i].Checks = make([]*structs.ServiceCheck, l)
1019			for j, check := range s.Checks {
1020				out[i].Checks[j] = &structs.ServiceCheck{
1021					Name:          check.Name,
1022					Type:          check.Type,
1023					Command:       check.Command,
1024					Args:          check.Args,
1025					Path:          check.Path,
1026					Protocol:      check.Protocol,
1027					PortLabel:     check.PortLabel,
1028					AddressMode:   check.AddressMode,
1029					Interval:      check.Interval,
1030					Timeout:       check.Timeout,
1031					InitialStatus: check.InitialStatus,
1032					TLSSkipVerify: check.TLSSkipVerify,
1033					Header:        check.Header,
1034					Method:        check.Method,
1035					GRPCService:   check.GRPCService,
1036					GRPCUseTLS:    check.GRPCUseTLS,
1037					TaskName:      check.TaskName,
1038				}
1039				if check.CheckRestart != nil {
1040					out[i].Checks[j].CheckRestart = &structs.CheckRestart{
1041						Limit:          check.CheckRestart.Limit,
1042						Grace:          *check.CheckRestart.Grace,
1043						IgnoreWarnings: check.CheckRestart.IgnoreWarnings,
1044					}
1045				}
1046			}
1047		}
1048
1049		if s.Connect != nil {
1050			out[i].Connect = ApiConsulConnectToStructs(s.Connect)
1051		}
1052
1053	}
1054
1055	return out
1056}
1057
1058func ApiConsulConnectToStructs(in *api.ConsulConnect) *structs.ConsulConnect {
1059	if in == nil {
1060		return nil
1061	}
1062
1063	out := &structs.ConsulConnect{
1064		Native: in.Native,
1065	}
1066
1067	if in.SidecarService != nil {
1068
1069		out.SidecarService = &structs.ConsulSidecarService{
1070			Tags: helper.CopySliceString(in.SidecarService.Tags),
1071			Port: in.SidecarService.Port,
1072		}
1073
1074		if in.SidecarService.Proxy != nil {
1075
1076			out.SidecarService.Proxy = &structs.ConsulProxy{
1077				LocalServiceAddress: in.SidecarService.Proxy.LocalServiceAddress,
1078				LocalServicePort:    in.SidecarService.Proxy.LocalServicePort,
1079				Config:              in.SidecarService.Proxy.Config,
1080			}
1081
1082			upstreams := make([]structs.ConsulUpstream, len(in.SidecarService.Proxy.Upstreams))
1083			for i, p := range in.SidecarService.Proxy.Upstreams {
1084				upstreams[i] = structs.ConsulUpstream{
1085					DestinationName: p.DestinationName,
1086					LocalBindPort:   p.LocalBindPort,
1087				}
1088			}
1089
1090			out.SidecarService.Proxy.Upstreams = upstreams
1091		}
1092	}
1093
1094	if in.SidecarTask != nil {
1095		out.SidecarTask = &structs.SidecarTask{
1096			Name:          in.SidecarTask.Name,
1097			Driver:        in.SidecarTask.Driver,
1098			Config:        in.SidecarTask.Config,
1099			User:          in.SidecarTask.User,
1100			Env:           in.SidecarTask.Env,
1101			Resources:     ApiResourcesToStructs(in.SidecarTask.Resources),
1102			Meta:          in.SidecarTask.Meta,
1103			LogConfig:     &structs.LogConfig{},
1104			ShutdownDelay: in.SidecarTask.ShutdownDelay,
1105			KillSignal:    in.SidecarTask.KillSignal,
1106		}
1107
1108		if in.SidecarTask.KillTimeout != nil {
1109			out.SidecarTask.KillTimeout = in.SidecarTask.KillTimeout
1110		}
1111		if in.SidecarTask.LogConfig != nil {
1112			out.SidecarTask.LogConfig = &structs.LogConfig{}
1113			if in.SidecarTask.LogConfig.MaxFiles != nil {
1114				out.SidecarTask.LogConfig.MaxFiles = *in.SidecarTask.LogConfig.MaxFiles
1115			}
1116			if in.SidecarTask.LogConfig.MaxFileSizeMB != nil {
1117				out.SidecarTask.LogConfig.MaxFileSizeMB = *in.SidecarTask.LogConfig.MaxFileSizeMB
1118			}
1119		}
1120	}
1121
1122	return out
1123}
1124
1125func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint {
1126	if in == nil {
1127		return nil
1128	}
1129
1130	out := make([]*structs.Constraint, len(in))
1131	for i, ac := range in {
1132		out[i] = ApiConstraintToStructs(ac)
1133	}
1134
1135	return out
1136}
1137
1138func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint {
1139	if in == nil {
1140		return nil
1141	}
1142
1143	return &structs.Constraint{
1144		LTarget: in.LTarget,
1145		RTarget: in.RTarget,
1146		Operand: in.Operand,
1147	}
1148}
1149
1150func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity {
1151	if in == nil {
1152		return nil
1153	}
1154
1155	out := make([]*structs.Affinity, len(in))
1156	for i, ac := range in {
1157		out[i] = ApiAffinityToStructs(ac)
1158	}
1159
1160	return out
1161}
1162
1163func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity {
1164	return &structs.Affinity{
1165		LTarget: a1.LTarget,
1166		Operand: a1.Operand,
1167		RTarget: a1.RTarget,
1168		Weight:  *a1.Weight,
1169	}
1170}
1171
1172func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread {
1173	ret := &structs.Spread{}
1174	ret.Attribute = a1.Attribute
1175	ret.Weight = *a1.Weight
1176	if a1.SpreadTarget != nil {
1177		ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget))
1178		for i, st := range a1.SpreadTarget {
1179			ret.SpreadTarget[i] = &structs.SpreadTarget{
1180				Value:   st.Value,
1181				Percent: st.Percent,
1182			}
1183		}
1184	}
1185	return ret
1186}
1187