From 60dcc0e3e681d62e30cbfc0d4ee2aa8ac89c4638 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 08:05:15 +0200 Subject: [PATCH 01/20] Adds Uid/Gid parameters to template. --- client/allocrunner/taskrunner/template/template.go | 6 ++++++ .../taskrunner/template/template_test.go | 14 ++++++++++++++ nomad/structs/structs.go | 3 +++ 3 files changed, 23 insertions(+) diff --git a/client/allocrunner/taskrunner/template/template.go b/client/allocrunner/taskrunner/template/template.go index 37c3f1da6eb..76fd910b0c7 100644 --- a/client/allocrunner/taskrunner/template/template.go +++ b/client/allocrunner/taskrunner/template/template.go @@ -626,6 +626,12 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.Templa m := os.FileMode(v) ct.Perms = &m } + // Set ownership + if tmpl.Uid != 0 && tmpl.Gid != 0 { + ct.Uid = &tmpl.Uid + ct.Gid = &tmpl.Gid + } + ct.Finalize() ctmpls[ct] = tmpl diff --git a/client/allocrunner/taskrunner/template/template_test.go b/client/allocrunner/taskrunner/template/template_test.go index 0221cfe1ca5..9b078466aef 100644 --- a/client/allocrunner/taskrunner/template/template_test.go +++ b/client/allocrunner/taskrunner/template/template_test.go @@ -16,6 +16,7 @@ import ( "strconv" "strings" "sync" + "syscall" "testing" "time" @@ -512,6 +513,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { DestPath: file, ChangeMode: structs.TemplateChangeModeNoop, Perms: "777", + Uid: 503, + Gid: 20, } harness := newTestHarness(t, []*structs.Template{template}, false, false) @@ -535,6 +538,17 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { if m := fi.Mode(); m != os.ModePerm { t.Fatalf("Got mode %v; want %v", m, os.ModePerm) } + + sys := fi.Sys() + uid := int(sys.(*syscall.Stat_t).Uid) + gid := int(sys.(*syscall.Stat_t).Gid) + + if uid != template.Uid { + t.Fatalf("Got uid #{uid}; want #{template.Uid}") + } + if gid != template.Gid { + t.Fatalf("Got gid #{uid}; want #{template.Gid}") + } } func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) { diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 800531e1b41..43596f5eb57 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -7644,6 +7644,9 @@ type Template struct { // Perms is the permission the file should be written out with. Perms string + // User and group that should own the file. + Uid int + Gid int // LeftDelim and RightDelim are optional configurations to control what // delimiter is utilized when parsing the template. From d96a142d506935b64600ceb638ad48e5958c34f6 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 09:57:50 +0200 Subject: [PATCH 02/20] Updated diff_test --- nomad/structs/diff_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 1a5751a8c7e..62cacbe33ba 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -7044,6 +7044,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", + Uid: 1001, + Gid: 21, Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(5 * time.Second), @@ -7057,6 +7059,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP2", Splay: 2, Perms: "0666", + Uid: 1000, + Gid: 20, Envvars: true, }, }, @@ -7071,6 +7075,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", + Uid: 1001, + Gid: 21, Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), @@ -7084,6 +7090,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP3", Splay: 3, Perms: "0776", + Uid: 1002, + Gid: 22, Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), @@ -7160,6 +7168,18 @@ func TestTaskDiff(t *testing.T) { Old: "", New: "0776", }, + { + Type: DiffTypeAdded, + Name: "Uid", + Old: "", + New: "1002", + }, + { + Type: DiffTypeAdded, + Name: "Gid", + Old: "", + New: "22", + }, { Type: DiffTypeAdded, Name: "SourcePath", @@ -7240,6 +7260,18 @@ func TestTaskDiff(t *testing.T) { Old: "0666", New: "", }, + { + Type: DiffTypeDeleted, + Name: "Uid", + Old: "1000", + New: "", + }, + { + Type: DiffTypeDeleted, + Name: "Gid", + Old: "20", + New: "", + }, { Type: DiffTypeDeleted, Name: "SourcePath", From 2fab60cf379a37eda1c910b2e5784a60ddad748f Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 10:05:09 +0200 Subject: [PATCH 03/20] fixed order --- nomad/structs/diff_test.go | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 62cacbe33ba..d081a72bed5 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -7164,21 +7164,15 @@ func TestTaskDiff(t *testing.T) { }, { Type: DiffTypeAdded, - Name: "Perms", - Old: "", - New: "0776", - }, - { - Type: DiffTypeAdded, - Name: "Uid", + Name: "Gid", Old: "", - New: "1002", + New: "22", }, { Type: DiffTypeAdded, - Name: "Gid", + Name: "Perms", Old: "", - New: "22", + New: "0776", }, { Type: DiffTypeAdded, @@ -7192,6 +7186,12 @@ func TestTaskDiff(t *testing.T) { Old: "", New: "3", }, + { + Type: DiffTypeAdded, + Name: "Uid", + Old: "", + New: "1002", + }, { Type: DiffTypeAdded, Name: "VaultGrace", @@ -7256,20 +7256,14 @@ func TestTaskDiff(t *testing.T) { }, { Type: DiffTypeDeleted, - Name: "Perms", - Old: "0666", - New: "", - }, - { - Type: DiffTypeDeleted, - Name: "Uid", - Old: "1000", + Name: "Gid", + Old: "20", New: "", }, { Type: DiffTypeDeleted, - Name: "Gid", - Old: "20", + Name: "Perms", + Old: "0666", New: "", }, { @@ -7284,6 +7278,12 @@ func TestTaskDiff(t *testing.T) { Old: "2", New: "", }, + { + Type: DiffTypeDeleted, + Name: "Uid", + Old: "1000", + New: "", + }, { Type: DiffTypeDeleted, Name: "VaultGrace", From 28bb58a816cd5e50bd85284d966336f370fdb95f Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 10:54:27 +0200 Subject: [PATCH 04/20] update jobspec and api --- api/tasks.go | 8 ++++++++ jobspec/helper.go | 20 ++++++++++++++++++++ jobspec/helper_test.go | 24 ------------------------ jobspec/parse_task.go | 4 ++++ jobspec/parse_test.go | 4 ++++ jobspec/test-fixtures/basic.hcl | 2 ++ jobspec2/helper_test.go | 5 ----- jobspec2/parse_job.go | 10 ++++++++++ 8 files changed, 48 insertions(+), 29 deletions(-) delete mode 100644 jobspec/helper_test.go delete mode 100644 jobspec2/helper_test.go diff --git a/api/tasks.go b/api/tasks.go index 6cdb44da3a3..6e38620df47 100644 --- a/api/tasks.go +++ b/api/tasks.go @@ -799,6 +799,8 @@ type Template struct { ChangeSignal *string `mapstructure:"change_signal" hcl:"change_signal,optional"` Splay *time.Duration `mapstructure:"splay" hcl:"splay,optional"` Perms *string `mapstructure:"perms" hcl:"perms,optional"` + Uid *int `mapstructure:"uid" hcl:"uid,optional"` + Gid *int `mapstructure:"gid" hcl:"gid,optional"` LeftDelim *string `mapstructure:"left_delimiter" hcl:"left_delimiter,optional"` RightDelim *string `mapstructure:"right_delimiter" hcl:"right_delimiter,optional"` Envvars *bool `mapstructure:"env" hcl:"env,optional"` @@ -835,6 +837,12 @@ func (tmpl *Template) Canonicalize() { if tmpl.Perms == nil { tmpl.Perms = stringToPtr("0644") } + if tmpl.Uid == nil { + tmpl.Uid = intToPtr(0) + } + if tmpl.Gid == nil { + tmpl.Gid = intToPtr(0) + } if tmpl.LeftDelim == nil { tmpl.LeftDelim = stringToPtr("{{") } diff --git a/jobspec/helper.go b/jobspec/helper.go index 99bf80f830f..bbc3d645247 100644 --- a/jobspec/helper.go +++ b/jobspec/helper.go @@ -18,6 +18,26 @@ func stringToPtr(str string) *string { return &str } +// intToPtr returns the pointer to an int +func intToPtr(i int) *int { + return &i +} + +// int8ToPtr returns the pointer to an int8 +func int8ToPtr(i int8) *int8 { + return &i +} + +// int64ToPtr returns the pointer to an int +func int64ToPtr(i int64) *int64 { + return &i +} + +// Uint64ToPtr returns the pointer to an uint64 +func uint64ToPtr(u uint64) *uint64 { + return &u +} + // timeToPtr returns the pointer to a time.Duration. func timeToPtr(t time.Duration) *time.Duration { return &t diff --git a/jobspec/helper_test.go b/jobspec/helper_test.go deleted file mode 100644 index f7854c80195..00000000000 --- a/jobspec/helper_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package jobspec - -// These functions are copied from helper/funcs.go -// added here to avoid jobspec depending on any other package - -// intToPtr returns the pointer to an int -func intToPtr(i int) *int { - return &i -} - -// int8ToPtr returns the pointer to an int8 -func int8ToPtr(i int8) *int8 { - return &i -} - -// int64ToPtr returns the pointer to an int -func int64ToPtr(i int64) *int64 { - return &i -} - -// Uint64ToPtr returns the pointer to an uint64 -func uint64ToPtr(u uint64) *uint64 { - return &u -} diff --git a/jobspec/parse_task.go b/jobspec/parse_task.go index 4bc77c310f2..87299146719 100644 --- a/jobspec/parse_task.go +++ b/jobspec/parse_task.go @@ -441,6 +441,8 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error { "destination", "left_delimiter", "perms", + "uid", + "gid", "right_delimiter", "source", "splay", @@ -460,6 +462,8 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error { ChangeMode: stringToPtr("restart"), Splay: timeToPtr(5 * time.Second), Perms: stringToPtr("0644"), + Uid: intToPtr(0), + Gid: intToPtr(0), } dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index 45d624aa22f..c4be7ea9e7f 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -363,6 +363,8 @@ func TestParse(t *testing.T) { ChangeSignal: stringToPtr("foo"), Splay: timeToPtr(10 * time.Second), Perms: stringToPtr("0644"), + Uid: intToPtr(0), + Gid: intToPtr(0), Envvars: boolToPtr(true), VaultGrace: timeToPtr(33 * time.Second), }, @@ -372,6 +374,8 @@ func TestParse(t *testing.T) { ChangeMode: stringToPtr(templateChangeModeRestart), Splay: timeToPtr(5 * time.Second), Perms: stringToPtr("777"), + Uid: intToPtr(1001), + Gid: intToPtr(20), LeftDelim: stringToPtr("--"), RightDelim: stringToPtr("__"), }, diff --git a/jobspec/test-fixtures/basic.hcl b/jobspec/test-fixtures/basic.hcl index 20a8171e4d0..273ae6ebdbf 100644 --- a/jobspec/test-fixtures/basic.hcl +++ b/jobspec/test-fixtures/basic.hcl @@ -318,6 +318,8 @@ job "binstore-storagelocker" { source = "bar" destination = "bar" perms = "777" + uid = 1001 + gid = 20 left_delimiter = "--" right_delimiter = "__" } diff --git a/jobspec2/helper_test.go b/jobspec2/helper_test.go deleted file mode 100644 index 57a6cd36028..00000000000 --- a/jobspec2/helper_test.go +++ /dev/null @@ -1,5 +0,0 @@ -package jobspec2 - -func intToPtr(v int) *int { - return &v -} diff --git a/jobspec2/parse_job.go b/jobspec2/parse_job.go index 9b533874f50..de19fe40936 100644 --- a/jobspec2/parse_job.go +++ b/jobspec2/parse_job.go @@ -107,6 +107,12 @@ func normalizeTemplates(templates []*api.Template) { if t.Perms == nil { t.Perms = stringToPtr("0644") } + if t.Uid == nil { + t.Uid = intToPtr(0) + } + if t.Gid == nil { + t.Gid = intToPtr(0) + } if t.Splay == nil { t.Splay = durationToPtr(5 * time.Second) } @@ -121,6 +127,10 @@ func boolToPtr(v bool) *bool { return &v } +func intToPtr(v int) *int { + return &v +} + func stringToPtr(v string) *string { return &v } From 866a6676c420f2a2a07e1b62391c220eb5142b23 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 10:57:55 +0200 Subject: [PATCH 05/20] removed obsolete code --- jobspec/helper.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/jobspec/helper.go b/jobspec/helper.go index bbc3d645247..826cba0652b 100644 --- a/jobspec/helper.go +++ b/jobspec/helper.go @@ -23,21 +23,6 @@ func intToPtr(i int) *int { return &i } -// int8ToPtr returns the pointer to an int8 -func int8ToPtr(i int8) *int8 { - return &i -} - -// int64ToPtr returns the pointer to an int -func int64ToPtr(i int64) *int64 { - return &i -} - -// Uint64ToPtr returns the pointer to an uint64 -func uint64ToPtr(u uint64) *uint64 { - return &u -} - // timeToPtr returns the pointer to a time.Duration. func timeToPtr(t time.Duration) *time.Duration { return &t From 07cfc80bade8d3aaad7c7d68b74c38fadda81148 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 11:09:20 +0200 Subject: [PATCH 06/20] helper functions for jobspec parse test --- jobspec/parse_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/jobspec/parse_test.go b/jobspec/parse_test.go index c4be7ea9e7f..fba7bb62c84 100644 --- a/jobspec/parse_test.go +++ b/jobspec/parse_test.go @@ -24,6 +24,17 @@ const ( templateChangeModeRestart = "restart" ) +// Helper functions below are only used by this test suite +func int8ToPtr(i int8) *int8 { + return &i +} +func uint64ToPtr(u uint64) *uint64 { + return &u +} +func int64ToPtr(i int64) *int64 { + return &i +} + func TestParse(t *testing.T) { ci.Parallel(t) From 944c72ed0ca3aa96474412e516a5683f043c71b0 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 11:15:33 +0200 Subject: [PATCH 07/20] updated documentation --- website/content/api-docs/json-jobs.mdx | 4 ++++ website/content/docs/job-specification/template.mdx | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/website/content/api-docs/json-jobs.mdx b/website/content/api-docs/json-jobs.mdx index 0223d38887e..bf04ea3dcd4 100644 --- a/website/content/api-docs/json-jobs.mdx +++ b/website/content/api-docs/json-jobs.mdx @@ -1077,6 +1077,10 @@ README][ct]. - `Perms` - Specifies the rendered template's permissions. File permissions are given as octal of the Unix file permissions `rwxrwxrwx`. +- `Uid` - Specifies the rendered template owner's user ID. + +- `Gid` - Specifies the rendered template owner's group ID. + - `RightDelim` - Specifies the right delimiter to use in the template. The default is "}}" for some templates, it may be easier to use a different delimiter that does not conflict with the output file itself. diff --git a/website/content/docs/job-specification/template.mdx b/website/content/docs/job-specification/template.mdx index 670f977950c..2391514b0b7 100644 --- a/website/content/docs/job-specification/template.mdx +++ b/website/content/docs/job-specification/template.mdx @@ -84,6 +84,10 @@ refer to the [Learn Go Template Syntax][gt_learn] Learn guide. - `perms` `(string: "644")` - Specifies the rendered template's permissions. File permissions are given as octal of the Unix file permissions `rwxrwxrwx`. +- `uid` `(int: 0)` - Specifies the rendered template owner's user ID. + +- `gid` `(int: 0)` - Specifies the rendered template owner's group ID. + - `right_delimiter` `(string: "}}")` - Specifies the right delimiter to use in the template. The default is "}}" for some templates, it may be easier to use a different delimiter that does not conflict with the output file itself. @@ -561,4 +565,4 @@ options](/docs/configuration/client#options): [task working directory]: /docs/runtime/environment#task-directories 'Task Directories' [filesystem internals]: /docs/concepts/filesystem#templates-artifacts-and-dispatch-payloads [`client.template.wait_bounds`]: /docs/configuration/client#wait_bounds -[rhash]: https://en.wikipedia.org/wiki/Rendezvous_hashing \ No newline at end of file +[rhash]: https://en.wikipedia.org/wiki/Rendezvous_hashing From 6c0806a6c7d9d2ec829de78061822700f5bfaa35 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Thu, 14 Jul 2022 17:01:20 +0200 Subject: [PATCH 08/20] adjusted API jobs test. --- api/jobs_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/api/jobs_test.go b/api/jobs_test.go index 6cc8b3abf17..b585a6ac9b7 100644 --- a/api/jobs_test.go +++ b/api/jobs_test.go @@ -765,6 +765,8 @@ func TestJobs_Canonicalize(t *testing.T) { ChangeSignal: stringToPtr(""), Splay: timeToPtr(5 * time.Second), Perms: stringToPtr("0644"), + Uid: intToPtr(0), + Gid: intToPtr(0), LeftDelim: stringToPtr("{{"), RightDelim: stringToPtr("}}"), Envvars: boolToPtr(false), @@ -778,6 +780,8 @@ func TestJobs_Canonicalize(t *testing.T) { ChangeSignal: stringToPtr(""), Splay: timeToPtr(5 * time.Second), Perms: stringToPtr("0644"), + Uid: intToPtr(0), + Gid: intToPtr(0), LeftDelim: stringToPtr("{{"), RightDelim: stringToPtr("}}"), Envvars: boolToPtr(true), From 258d0c548cd46632e58e7d76c5b22c8e24b103f9 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Fri, 15 Jul 2022 08:44:47 +0200 Subject: [PATCH 09/20] propagate uid/gid setting to job_endpoint --- command/agent/job_endpoint.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index f7a74c4f8d0..d86181bae7b 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -1218,6 +1218,8 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup, ChangeSignal: *template.ChangeSignal, Splay: *template.Splay, Perms: *template.Perms, + Uid: *template.Uid, + Gid: *template.Gid, LeftDelim: *template.LeftDelim, RightDelim: *template.RightDelim, Envvars: *template.Envvars, From 65db83a3ecfdc283e983ff4e7c94a077bc1a0827 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Fri, 15 Jul 2022 08:50:05 +0200 Subject: [PATCH 10/20] adjusted job_endpoint tests --- command/agent/job_endpoint_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/command/agent/job_endpoint_test.go b/command/agent/job_endpoint_test.go index 28e08ed3181..bb30f91fb2e 100644 --- a/command/agent/job_endpoint_test.go +++ b/command/agent/job_endpoint_test.go @@ -2733,6 +2733,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) { ChangeSignal: helper.StringToPtr("signal"), Splay: helper.TimeToPtr(1 * time.Minute), Perms: helper.StringToPtr("666"), + Uid: helper.IntToPtr(1000), + Gid: helper.IntToPtr(1000), LeftDelim: helper.StringToPtr("abc"), RightDelim: helper.StringToPtr("def"), Envvars: helper.BoolToPtr(true), @@ -3138,6 +3140,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) { ChangeSignal: "SIGNAL", Splay: 1 * time.Minute, Perms: "666", + Uid: 1000, + Gid: 1000, LeftDelim: "abc", RightDelim: "def", Envvars: true, From 0d6425e64be1fb81d73e2ec1fb6b35a5415a4dcd Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Fri, 15 Jul 2022 16:07:34 +0200 Subject: [PATCH 11/20] making uid/gid into pointers --- .../allocrunner/taskrunner/template/template.go | 6 +++--- .../taskrunner/template/template_test.go | 9 +++++---- command/agent/job_endpoint.go | 4 ++-- command/agent/job_endpoint_test.go | 5 +++-- nomad/structs/diff_test.go | 17 +++++++++-------- nomad/structs/structs.go | 4 ++-- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/client/allocrunner/taskrunner/template/template.go b/client/allocrunner/taskrunner/template/template.go index 76fd910b0c7..b6f33bc07fe 100644 --- a/client/allocrunner/taskrunner/template/template.go +++ b/client/allocrunner/taskrunner/template/template.go @@ -627,9 +627,9 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.Templa ct.Perms = &m } // Set ownership - if tmpl.Uid != 0 && tmpl.Gid != 0 { - ct.Uid = &tmpl.Uid - ct.Gid = &tmpl.Gid + if tmpl.Uid != nil && tmpl.Gid != nil { + ct.Uid = tmpl.Uid + ct.Gid = tmpl.Gid } ct.Finalize() diff --git a/client/allocrunner/taskrunner/template/template_test.go b/client/allocrunner/taskrunner/template/template_test.go index 9b078466aef..dd77585d1d6 100644 --- a/client/allocrunner/taskrunner/template/template_test.go +++ b/client/allocrunner/taskrunner/template/template_test.go @@ -27,6 +27,7 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/taskenv" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/mock" @@ -513,8 +514,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { DestPath: file, ChangeMode: structs.TemplateChangeModeNoop, Perms: "777", - Uid: 503, - Gid: 20, + Uid: pointer.Of(503), + Gid: pointer.Of(20), } harness := newTestHarness(t, []*structs.Template{template}, false, false) @@ -543,10 +544,10 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { uid := int(sys.(*syscall.Stat_t).Uid) gid := int(sys.(*syscall.Stat_t).Gid) - if uid != template.Uid { + if uid != *template.Uid { t.Fatalf("Got uid #{uid}; want #{template.Uid}") } - if gid != template.Gid { + if gid != *template.Gid { t.Fatalf("Got gid #{uid}; want #{template.Gid}") } } diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index d86181bae7b..621cb682118 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -1218,8 +1218,8 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup, ChangeSignal: *template.ChangeSignal, Splay: *template.Splay, Perms: *template.Perms, - Uid: *template.Uid, - Gid: *template.Gid, + Uid: template.Uid, + Gid: template.Gid, LeftDelim: *template.LeftDelim, RightDelim: *template.RightDelim, Envvars: *template.Envvars, diff --git a/command/agent/job_endpoint_test.go b/command/agent/job_endpoint_test.go index bb30f91fb2e..61a265e3693 100644 --- a/command/agent/job_endpoint_test.go +++ b/command/agent/job_endpoint_test.go @@ -14,6 +14,7 @@ import ( api "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/stretchr/testify/assert" @@ -3140,8 +3141,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) { ChangeSignal: "SIGNAL", Splay: 1 * time.Minute, Perms: "666", - Uid: 1000, - Gid: 1000, + Uid: pointer.Of(1000), + Gid: pointer.Of(1000), LeftDelim: "abc", RightDelim: "def", Envvars: true, diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index d081a72bed5..9bb13cddfe3 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper" + "github.com/hashicorp/nomad/helper/pointer" "github.com/stretchr/testify/require" ) @@ -7044,8 +7045,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", - Uid: 1001, - Gid: 21, + Uid: pointer.Of(1001), + Gid: pointer.Of(21), Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(5 * time.Second), @@ -7059,8 +7060,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP2", Splay: 2, Perms: "0666", - Uid: 1000, - Gid: 20, + Uid: pointer.Of(1000), + Gid: pointer.Of(20), Envvars: true, }, }, @@ -7075,8 +7076,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", - Uid: 1001, - Gid: 21, + Uid: pointer.Of(1001), + Gid: pointer.Of(21), Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), @@ -7090,8 +7091,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP3", Splay: 3, Perms: "0776", - Uid: 1002, - Gid: 22, + Uid: pointer.Of(1002), + Gid: pointer.Of(22), Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 43596f5eb57..4ba8eeb7e76 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -7645,8 +7645,8 @@ type Template struct { // Perms is the permission the file should be written out with. Perms string // User and group that should own the file. - Uid int - Gid int + Uid *int + Gid *int // LeftDelim and RightDelim are optional configurations to control what // delimiter is utilized when parsing the template. From 38f0a02b2024118b48058e06053882f82ce0d9cf Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Fri, 15 Jul 2022 18:26:16 +0200 Subject: [PATCH 12/20] refactor --- .../allocrunner/taskrunner/template/template.go | 6 +++--- .../taskrunner/template/template_test.go | 9 ++++----- command/agent/job_endpoint.go | 12 ++++++++++-- command/agent/job_endpoint_test.go | 5 ++--- nomad/structs/diff_test.go | 17 ++++++++--------- nomad/structs/structs.go | 4 ++-- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/client/allocrunner/taskrunner/template/template.go b/client/allocrunner/taskrunner/template/template.go index b6f33bc07fe..3d4308c707d 100644 --- a/client/allocrunner/taskrunner/template/template.go +++ b/client/allocrunner/taskrunner/template/template.go @@ -627,9 +627,9 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.Templa ct.Perms = &m } // Set ownership - if tmpl.Uid != nil && tmpl.Gid != nil { - ct.Uid = tmpl.Uid - ct.Gid = tmpl.Gid + if tmpl.Uid >= 0 && tmpl.Gid >= 0 { + ct.Uid = &tmpl.Uid + ct.Gid = &tmpl.Gid } ct.Finalize() diff --git a/client/allocrunner/taskrunner/template/template_test.go b/client/allocrunner/taskrunner/template/template_test.go index dd77585d1d6..9b078466aef 100644 --- a/client/allocrunner/taskrunner/template/template_test.go +++ b/client/allocrunner/taskrunner/template/template_test.go @@ -27,7 +27,6 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/taskenv" "github.com/hashicorp/nomad/helper" - "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/helper/uuid" "github.com/hashicorp/nomad/nomad/mock" @@ -514,8 +513,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { DestPath: file, ChangeMode: structs.TemplateChangeModeNoop, Perms: "777", - Uid: pointer.Of(503), - Gid: pointer.Of(20), + Uid: 503, + Gid: 20, } harness := newTestHarness(t, []*structs.Template{template}, false, false) @@ -544,10 +543,10 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { uid := int(sys.(*syscall.Stat_t).Uid) gid := int(sys.(*syscall.Stat_t).Gid) - if uid != *template.Uid { + if uid != template.Uid { t.Fatalf("Got uid #{uid}; want #{template.Uid}") } - if gid != *template.Gid { + if gid != template.Gid { t.Fatalf("Got gid #{uid}; want #{template.Gid}") } } diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index 621cb682118..9c85e4b1cdf 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -1209,6 +1209,14 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup, if len(apiTask.Templates) > 0 { structsTask.Templates = []*structs.Template{} for _, template := range apiTask.Templates { + uid := -1 + if template.Uid != nil { + uid = *template.Uid + } + gid := -1 + if template.Gid != nil { + gid = *template.Gid + } structsTask.Templates = append(structsTask.Templates, &structs.Template{ SourcePath: *template.SourcePath, @@ -1218,8 +1226,8 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup, ChangeSignal: *template.ChangeSignal, Splay: *template.Splay, Perms: *template.Perms, - Uid: template.Uid, - Gid: template.Gid, + Uid: uid, + Gid: gid, LeftDelim: *template.LeftDelim, RightDelim: *template.RightDelim, Envvars: *template.Envvars, diff --git a/command/agent/job_endpoint_test.go b/command/agent/job_endpoint_test.go index 61a265e3693..bb30f91fb2e 100644 --- a/command/agent/job_endpoint_test.go +++ b/command/agent/job_endpoint_test.go @@ -14,7 +14,6 @@ import ( api "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper" - "github.com/hashicorp/nomad/helper/pointer" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/stretchr/testify/assert" @@ -3141,8 +3140,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) { ChangeSignal: "SIGNAL", Splay: 1 * time.Minute, Perms: "666", - Uid: pointer.Of(1000), - Gid: pointer.Of(1000), + Uid: 1000, + Gid: 1000, LeftDelim: "abc", RightDelim: "def", Envvars: true, diff --git a/nomad/structs/diff_test.go b/nomad/structs/diff_test.go index 9bb13cddfe3..d081a72bed5 100644 --- a/nomad/structs/diff_test.go +++ b/nomad/structs/diff_test.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper" - "github.com/hashicorp/nomad/helper/pointer" "github.com/stretchr/testify/require" ) @@ -7045,8 +7044,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", - Uid: pointer.Of(1001), - Gid: pointer.Of(21), + Uid: 1001, + Gid: 21, Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(5 * time.Second), @@ -7060,8 +7059,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP2", Splay: 2, Perms: "0666", - Uid: pointer.Of(1000), - Gid: pointer.Of(20), + Uid: 1000, + Gid: 20, Envvars: true, }, }, @@ -7076,8 +7075,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP", Splay: 1, Perms: "0644", - Uid: pointer.Of(1001), - Gid: pointer.Of(21), + Uid: 1001, + Gid: 21, Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), @@ -7091,8 +7090,8 @@ func TestTaskDiff(t *testing.T) { ChangeSignal: "SIGHUP3", Splay: 3, Perms: "0776", - Uid: pointer.Of(1002), - Gid: pointer.Of(22), + Uid: 1002, + Gid: 22, Wait: &WaitConfig{ Min: helper.TimeToPtr(5 * time.Second), Max: helper.TimeToPtr(10 * time.Second), diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 4ba8eeb7e76..43596f5eb57 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -7645,8 +7645,8 @@ type Template struct { // Perms is the permission the file should be written out with. Perms string // User and group that should own the file. - Uid *int - Gid *int + Uid int + Gid int // LeftDelim and RightDelim are optional configurations to control what // delimiter is utilized when parsing the template. From 3f5d89951794ec92a8407e2145c38b81af198ff5 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Tue, 2 Aug 2022 17:04:25 +0200 Subject: [PATCH 13/20] updated documentation --- website/content/api-docs/json-jobs.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/content/api-docs/json-jobs.mdx b/website/content/api-docs/json-jobs.mdx index bf04ea3dcd4..40300a71786 100644 --- a/website/content/api-docs/json-jobs.mdx +++ b/website/content/api-docs/json-jobs.mdx @@ -1078,8 +1078,16 @@ README][ct]. given as octal of the Unix file permissions `rwxrwxrwx`. - `Uid` - Specifies the rendered template owner's user ID. + + ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as + groups and users inside the container may have different IDs than on the host + system. This feature will also **not** work with Docker Desktop. - `Gid` - Specifies the rendered template owner's group ID. + + ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as + groups and users inside the container may have different IDs than on the host + system. This feature will also **not** work with Docker Desktop. - `RightDelim` - Specifies the right delimiter to use in the template. The default is "}}" for some templates, it may be easier to use a different delimiter that From 01f56ce981835f12dbd5c9997e406bf67554e4ee Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Tue, 2 Aug 2022 17:08:53 +0200 Subject: [PATCH 14/20] updated documentation --- website/content/docs/job-specification/template.mdx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/website/content/docs/job-specification/template.mdx b/website/content/docs/job-specification/template.mdx index 2391514b0b7..87bf5eb3d94 100644 --- a/website/content/docs/job-specification/template.mdx +++ b/website/content/docs/job-specification/template.mdx @@ -85,9 +85,17 @@ refer to the [Learn Go Template Syntax][gt_learn] Learn guide. File permissions are given as octal of the Unix file permissions `rwxrwxrwx`. - `uid` `(int: 0)` - Specifies the rendered template owner's user ID. + + ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as + groups and users inside the container may have different IDs than on the host + system. This feature will also **not** work with Docker Desktop. - `gid` `(int: 0)` - Specifies the rendered template owner's group ID. + ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as + groups and users inside the container may have different IDs than on the host + system. This feature will also **not** work with Docker Desktop. + - `right_delimiter` `(string: "}}")` - Specifies the right delimiter to use in the template. The default is "}}" for some templates, it may be easier to use a different delimiter that does not conflict with the output file itself. From ba95feb06638e89db410b712abd12a79bad6444e Mon Sep 17 00:00:00 2001 From: Piotr Kazmierczak Date: Tue, 2 Aug 2022 21:02:50 +0200 Subject: [PATCH 15/20] Update client/allocrunner/taskrunner/template/template_test.go Co-authored-by: Luiz Aoqui --- client/allocrunner/taskrunner/template/template_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/client/allocrunner/taskrunner/template/template_test.go b/client/allocrunner/taskrunner/template/template_test.go index 9b078466aef..4da7fc83862 100644 --- a/client/allocrunner/taskrunner/template/template_test.go +++ b/client/allocrunner/taskrunner/template/template_test.go @@ -543,12 +543,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { uid := int(sys.(*syscall.Stat_t).Uid) gid := int(sys.(*syscall.Stat_t).Gid) - if uid != template.Uid { - t.Fatalf("Got uid #{uid}; want #{template.Uid}") - } - if gid != template.Gid { - t.Fatalf("Got gid #{uid}; want #{template.Gid}") - } +must.Eq(t, template.Uid, uid) +must.Eq(t, template.Gid, gid) } func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) { From 3e2aa5e05f5e68c36a8b86f241abad46a0fa3aab Mon Sep 17 00:00:00 2001 From: Piotr Kazmierczak Date: Tue, 2 Aug 2022 21:02:59 +0200 Subject: [PATCH 16/20] Update website/content/api-docs/json-jobs.mdx Co-authored-by: Luiz Aoqui --- website/content/api-docs/json-jobs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/api-docs/json-jobs.mdx b/website/content/api-docs/json-jobs.mdx index 40300a71786..6df247ed395 100644 --- a/website/content/api-docs/json-jobs.mdx +++ b/website/content/api-docs/json-jobs.mdx @@ -1079,7 +1079,7 @@ README][ct]. - `Uid` - Specifies the rendered template owner's user ID. - ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as + ~> **Caveat:** Works only on Unix-based systems. Be careful when using containerized drivers, suck as `docker` or `podman`, as groups and users inside the container may have different IDs than on the host system. This feature will also **not** work with Docker Desktop. From ed3b78c5b1d9658f34df1775c610feaedf53f372 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Tue, 2 Aug 2022 21:04:15 +0200 Subject: [PATCH 17/20] propagating documentation change from Luiz --- website/content/api-docs/json-jobs.mdx | 14 ++++++++------ .../content/docs/job-specification/template.mdx | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/website/content/api-docs/json-jobs.mdx b/website/content/api-docs/json-jobs.mdx index 6df247ed395..ffc7c89a116 100644 --- a/website/content/api-docs/json-jobs.mdx +++ b/website/content/api-docs/json-jobs.mdx @@ -1079,15 +1079,17 @@ README][ct]. - `Uid` - Specifies the rendered template owner's user ID. - ~> **Caveat:** Works only on Unix-based systems. Be careful when using containerized drivers, suck as `docker` or `podman`, as - groups and users inside the container may have different IDs than on the host - system. This feature will also **not** work with Docker Desktop. + ~> **Caveat:** Works only on Unix-based systems. Be careful when using + containerized drivers, suck as `docker` or `podman`, as groups and users + inside the container may have different IDs than on the host system. This + feature will also **not** work with Docker Desktop. - `Gid` - Specifies the rendered template owner's group ID. - ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as - groups and users inside the container may have different IDs than on the host - system. This feature will also **not** work with Docker Desktop. + ~> **Caveat:** Works only on Unix-based systems. Be careful when using + containerized drivers, suck as `docker` or `podman`, as groups and users + inside the container may have different IDs than on the host system. This + feature will also **not** work with Docker Desktop. - `RightDelim` - Specifies the right delimiter to use in the template. The default is "}}" for some templates, it may be easier to use a different delimiter that diff --git a/website/content/docs/job-specification/template.mdx b/website/content/docs/job-specification/template.mdx index 87bf5eb3d94..8704bce7b1c 100644 --- a/website/content/docs/job-specification/template.mdx +++ b/website/content/docs/job-specification/template.mdx @@ -86,15 +86,17 @@ refer to the [Learn Go Template Syntax][gt_learn] Learn guide. - `uid` `(int: 0)` - Specifies the rendered template owner's user ID. - ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as - groups and users inside the container may have different IDs than on the host - system. This feature will also **not** work with Docker Desktop. + ~> **Caveat:** Works only on Unix-based systems. Be careful when using + containerized drivers, suck as `docker` or `podman`, as groups and users + inside the container may have different IDs than on the host system. This + feature will also **not** work with Docker Desktop. - `gid` `(int: 0)` - Specifies the rendered template owner's group ID. - ~> **Caveat:** Works only on Linux. Be careful when using the docker driver as - groups and users inside the container may have different IDs than on the host - system. This feature will also **not** work with Docker Desktop. + ~> **Caveat:** Works only on Unix-based systems. Be careful when using + containerized drivers, suck as `docker` or `podman`, as groups and users + inside the container may have different IDs than on the host system. This + feature will also **not** work with Docker Desktop. - `right_delimiter` `(string: "}}")` - Specifies the right delimiter to use in the template. The default is "}}" for some templates, it may be easier to use a From 265afb9ca30c45d7199f931a18eb2ccc312234eb Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Tue, 2 Aug 2022 21:05:32 +0200 Subject: [PATCH 18/20] formatting --- client/allocrunner/taskrunner/template/template_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/allocrunner/taskrunner/template/template_test.go b/client/allocrunner/taskrunner/template/template_test.go index 4da7fc83862..2c679ba1fae 100644 --- a/client/allocrunner/taskrunner/template/template_test.go +++ b/client/allocrunner/taskrunner/template/template_test.go @@ -34,6 +34,7 @@ import ( sconfig "github.com/hashicorp/nomad/nomad/structs/config" "github.com/hashicorp/nomad/testutil" "github.com/kr/pretty" + "github.com/shoenig/test/must" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -543,8 +544,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) { uid := int(sys.(*syscall.Stat_t).Uid) gid := int(sys.(*syscall.Stat_t).Gid) -must.Eq(t, template.Uid, uid) -must.Eq(t, template.Gid, gid) + must.Eq(t, template.Uid, uid) + must.Eq(t, template.Gid, gid) } func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) { From 7a9a31a8592bd346bca28164cf95774ce1698de3 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Tue, 2 Aug 2022 21:12:48 +0200 Subject: [PATCH 19/20] changelog entry --- .changelog/13755.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/13755.txt diff --git a/.changelog/13755.txt b/.changelog/13755.txt new file mode 100644 index 00000000000..5ea3d80f4e6 --- /dev/null +++ b/.changelog/13755.txt @@ -0,0 +1,3 @@ +```release-note:improvement +client: Templates support new uid/gid parameter pair +``` \ No newline at end of file From 15b170f14834c1303dfce7f4c55fffa286bb6251 Mon Sep 17 00:00:00 2001 From: Peter Kazmierczak Date: Tue, 2 Aug 2022 21:46:35 +0200 Subject: [PATCH 20/20] changed changelog entry --- .changelog/13755.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/13755.txt b/.changelog/13755.txt index 5ea3d80f4e6..3b1c2c051ae 100644 --- a/.changelog/13755.txt +++ b/.changelog/13755.txt @@ -1,3 +1,3 @@ ```release-note:improvement -client: Templates support new uid/gid parameter pair +template: Templates support new uid/gid parameter pair ``` \ No newline at end of file