Skip to content

Commit 2e0b875

Browse files
pkazmierczaklgfa29
andauthored
client: enable specifying user/group permissions in the template stanza (#13755)
* Adds Uid/Gid parameters to template. * Updated diff_test * fixed order * update jobspec and api * removed obsolete code * helper functions for jobspec parse test * updated documentation * adjusted API jobs test. * propagate uid/gid setting to job_endpoint * adjusted job_endpoint tests * making uid/gid into pointers * refactor * updated documentation * updated documentation * Update client/allocrunner/taskrunner/template/template_test.go Co-authored-by: Luiz Aoqui <[email protected]> * Update website/content/api-docs/json-jobs.mdx Co-authored-by: Luiz Aoqui <[email protected]> * propagating documentation change from Luiz * formatting * changelog entry * changed changelog entry Co-authored-by: Luiz Aoqui <[email protected]>
1 parent 9dd905d commit 2e0b875

File tree

18 files changed

+146
-30
lines changed

18 files changed

+146
-30
lines changed

.changelog/13755.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
template: Templates support new uid/gid parameter pair
3+
```

api/jobs_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ func TestJobs_Canonicalize(t *testing.T) {
765765
ChangeSignal: stringToPtr(""),
766766
Splay: timeToPtr(5 * time.Second),
767767
Perms: stringToPtr("0644"),
768+
Uid: intToPtr(0),
769+
Gid: intToPtr(0),
768770
LeftDelim: stringToPtr("{{"),
769771
RightDelim: stringToPtr("}}"),
770772
Envvars: boolToPtr(false),
@@ -778,6 +780,8 @@ func TestJobs_Canonicalize(t *testing.T) {
778780
ChangeSignal: stringToPtr(""),
779781
Splay: timeToPtr(5 * time.Second),
780782
Perms: stringToPtr("0644"),
783+
Uid: intToPtr(0),
784+
Gid: intToPtr(0),
781785
LeftDelim: stringToPtr("{{"),
782786
RightDelim: stringToPtr("}}"),
783787
Envvars: boolToPtr(true),

api/tasks.go

+8
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,8 @@ type Template struct {
799799
ChangeSignal *string `mapstructure:"change_signal" hcl:"change_signal,optional"`
800800
Splay *time.Duration `mapstructure:"splay" hcl:"splay,optional"`
801801
Perms *string `mapstructure:"perms" hcl:"perms,optional"`
802+
Uid *int `mapstructure:"uid" hcl:"uid,optional"`
803+
Gid *int `mapstructure:"gid" hcl:"gid,optional"`
802804
LeftDelim *string `mapstructure:"left_delimiter" hcl:"left_delimiter,optional"`
803805
RightDelim *string `mapstructure:"right_delimiter" hcl:"right_delimiter,optional"`
804806
Envvars *bool `mapstructure:"env" hcl:"env,optional"`
@@ -835,6 +837,12 @@ func (tmpl *Template) Canonicalize() {
835837
if tmpl.Perms == nil {
836838
tmpl.Perms = stringToPtr("0644")
837839
}
840+
if tmpl.Uid == nil {
841+
tmpl.Uid = intToPtr(0)
842+
}
843+
if tmpl.Gid == nil {
844+
tmpl.Gid = intToPtr(0)
845+
}
838846
if tmpl.LeftDelim == nil {
839847
tmpl.LeftDelim = stringToPtr("{{")
840848
}

client/allocrunner/taskrunner/template/template.go

+6
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,12 @@ func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.Templa
626626
m := os.FileMode(v)
627627
ct.Perms = &m
628628
}
629+
// Set ownership
630+
if tmpl.Uid >= 0 && tmpl.Gid >= 0 {
631+
ct.Uid = &tmpl.Uid
632+
ct.Gid = &tmpl.Gid
633+
}
634+
629635
ct.Finalize()
630636

631637
ctmpls[ct] = tmpl

client/allocrunner/taskrunner/template/template_test.go

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"strconv"
1717
"strings"
1818
"sync"
19+
"syscall"
1920
"testing"
2021
"time"
2122

@@ -33,6 +34,7 @@ import (
3334
sconfig "github.com/hashicorp/nomad/nomad/structs/config"
3435
"github.com/hashicorp/nomad/testutil"
3536
"github.com/kr/pretty"
37+
"github.com/shoenig/test/must"
3638
"github.com/stretchr/testify/assert"
3739
"github.com/stretchr/testify/require"
3840
)
@@ -512,6 +514,8 @@ func TestTaskTemplateManager_Permissions(t *testing.T) {
512514
DestPath: file,
513515
ChangeMode: structs.TemplateChangeModeNoop,
514516
Perms: "777",
517+
Uid: 503,
518+
Gid: 20,
515519
}
516520

517521
harness := newTestHarness(t, []*structs.Template{template}, false, false)
@@ -535,6 +539,13 @@ func TestTaskTemplateManager_Permissions(t *testing.T) {
535539
if m := fi.Mode(); m != os.ModePerm {
536540
t.Fatalf("Got mode %v; want %v", m, os.ModePerm)
537541
}
542+
543+
sys := fi.Sys()
544+
uid := int(sys.(*syscall.Stat_t).Uid)
545+
gid := int(sys.(*syscall.Stat_t).Gid)
546+
547+
must.Eq(t, template.Uid, uid)
548+
must.Eq(t, template.Gid, gid)
538549
}
539550

540551
func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) {

command/agent/job_endpoint.go

+10
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,14 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup,
12091209
if len(apiTask.Templates) > 0 {
12101210
structsTask.Templates = []*structs.Template{}
12111211
for _, template := range apiTask.Templates {
1212+
uid := -1
1213+
if template.Uid != nil {
1214+
uid = *template.Uid
1215+
}
1216+
gid := -1
1217+
if template.Gid != nil {
1218+
gid = *template.Gid
1219+
}
12121220
structsTask.Templates = append(structsTask.Templates,
12131221
&structs.Template{
12141222
SourcePath: *template.SourcePath,
@@ -1218,6 +1226,8 @@ func ApiTaskToStructsTask(job *structs.Job, group *structs.TaskGroup,
12181226
ChangeSignal: *template.ChangeSignal,
12191227
Splay: *template.Splay,
12201228
Perms: *template.Perms,
1229+
Uid: uid,
1230+
Gid: gid,
12211231
LeftDelim: *template.LeftDelim,
12221232
RightDelim: *template.RightDelim,
12231233
Envvars: *template.Envvars,

command/agent/job_endpoint_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
27332733
ChangeSignal: helper.StringToPtr("signal"),
27342734
Splay: helper.TimeToPtr(1 * time.Minute),
27352735
Perms: helper.StringToPtr("666"),
2736+
Uid: helper.IntToPtr(1000),
2737+
Gid: helper.IntToPtr(1000),
27362738
LeftDelim: helper.StringToPtr("abc"),
27372739
RightDelim: helper.StringToPtr("def"),
27382740
Envvars: helper.BoolToPtr(true),
@@ -3138,6 +3140,8 @@ func TestJobs_ApiJobToStructsJob(t *testing.T) {
31383140
ChangeSignal: "SIGNAL",
31393141
Splay: 1 * time.Minute,
31403142
Perms: "666",
3143+
Uid: 1000,
3144+
Gid: 1000,
31413145
LeftDelim: "abc",
31423146
RightDelim: "def",
31433147
Envvars: true,

jobspec/helper.go

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ func stringToPtr(str string) *string {
1818
return &str
1919
}
2020

21+
// intToPtr returns the pointer to an int
22+
func intToPtr(i int) *int {
23+
return &i
24+
}
25+
2126
// timeToPtr returns the pointer to a time.Duration.
2227
func timeToPtr(t time.Duration) *time.Duration {
2328
return &t

jobspec/helper_test.go

-24
This file was deleted.

jobspec/parse_task.go

+4
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
441441
"destination",
442442
"left_delimiter",
443443
"perms",
444+
"uid",
445+
"gid",
444446
"right_delimiter",
445447
"source",
446448
"splay",
@@ -460,6 +462,8 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
460462
ChangeMode: stringToPtr("restart"),
461463
Splay: timeToPtr(5 * time.Second),
462464
Perms: stringToPtr("0644"),
465+
Uid: intToPtr(0),
466+
Gid: intToPtr(0),
463467
}
464468

465469
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{

jobspec/parse_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ const (
2424
templateChangeModeRestart = "restart"
2525
)
2626

27+
// Helper functions below are only used by this test suite
28+
func int8ToPtr(i int8) *int8 {
29+
return &i
30+
}
31+
func uint64ToPtr(u uint64) *uint64 {
32+
return &u
33+
}
34+
func int64ToPtr(i int64) *int64 {
35+
return &i
36+
}
37+
2738
func TestParse(t *testing.T) {
2839
ci.Parallel(t)
2940

@@ -363,6 +374,8 @@ func TestParse(t *testing.T) {
363374
ChangeSignal: stringToPtr("foo"),
364375
Splay: timeToPtr(10 * time.Second),
365376
Perms: stringToPtr("0644"),
377+
Uid: intToPtr(0),
378+
Gid: intToPtr(0),
366379
Envvars: boolToPtr(true),
367380
VaultGrace: timeToPtr(33 * time.Second),
368381
},
@@ -372,6 +385,8 @@ func TestParse(t *testing.T) {
372385
ChangeMode: stringToPtr(templateChangeModeRestart),
373386
Splay: timeToPtr(5 * time.Second),
374387
Perms: stringToPtr("777"),
388+
Uid: intToPtr(1001),
389+
Gid: intToPtr(20),
375390
LeftDelim: stringToPtr("--"),
376391
RightDelim: stringToPtr("__"),
377392
},

jobspec/test-fixtures/basic.hcl

+2
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ job "binstore-storagelocker" {
318318
source = "bar"
319319
destination = "bar"
320320
perms = "777"
321+
uid = 1001
322+
gid = 20
321323
left_delimiter = "--"
322324
right_delimiter = "__"
323325
}

jobspec2/helper_test.go

-5
This file was deleted.

jobspec2/parse_job.go

+10
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ func normalizeTemplates(templates []*api.Template) {
107107
if t.Perms == nil {
108108
t.Perms = stringToPtr("0644")
109109
}
110+
if t.Uid == nil {
111+
t.Uid = intToPtr(0)
112+
}
113+
if t.Gid == nil {
114+
t.Gid = intToPtr(0)
115+
}
110116
if t.Splay == nil {
111117
t.Splay = durationToPtr(5 * time.Second)
112118
}
@@ -121,6 +127,10 @@ func boolToPtr(v bool) *bool {
121127
return &v
122128
}
123129

130+
func intToPtr(v int) *int {
131+
return &v
132+
}
133+
124134
func stringToPtr(v string) *string {
125135
return &v
126136
}

nomad/structs/diff_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -7044,6 +7044,8 @@ func TestTaskDiff(t *testing.T) {
70447044
ChangeSignal: "SIGHUP",
70457045
Splay: 1,
70467046
Perms: "0644",
7047+
Uid: 1001,
7048+
Gid: 21,
70477049
Wait: &WaitConfig{
70487050
Min: helper.TimeToPtr(5 * time.Second),
70497051
Max: helper.TimeToPtr(5 * time.Second),
@@ -7057,6 +7059,8 @@ func TestTaskDiff(t *testing.T) {
70577059
ChangeSignal: "SIGHUP2",
70587060
Splay: 2,
70597061
Perms: "0666",
7062+
Uid: 1000,
7063+
Gid: 20,
70607064
Envvars: true,
70617065
},
70627066
},
@@ -7071,6 +7075,8 @@ func TestTaskDiff(t *testing.T) {
70717075
ChangeSignal: "SIGHUP",
70727076
Splay: 1,
70737077
Perms: "0644",
7078+
Uid: 1001,
7079+
Gid: 21,
70747080
Wait: &WaitConfig{
70757081
Min: helper.TimeToPtr(5 * time.Second),
70767082
Max: helper.TimeToPtr(10 * time.Second),
@@ -7084,6 +7090,8 @@ func TestTaskDiff(t *testing.T) {
70847090
ChangeSignal: "SIGHUP3",
70857091
Splay: 3,
70867092
Perms: "0776",
7093+
Uid: 1002,
7094+
Gid: 22,
70877095
Wait: &WaitConfig{
70887096
Min: helper.TimeToPtr(5 * time.Second),
70897097
Max: helper.TimeToPtr(10 * time.Second),
@@ -7154,6 +7162,12 @@ func TestTaskDiff(t *testing.T) {
71547162
Old: "",
71557163
New: "false",
71567164
},
7165+
{
7166+
Type: DiffTypeAdded,
7167+
Name: "Gid",
7168+
Old: "",
7169+
New: "22",
7170+
},
71577171
{
71587172
Type: DiffTypeAdded,
71597173
Name: "Perms",
@@ -7172,6 +7186,12 @@ func TestTaskDiff(t *testing.T) {
71727186
Old: "",
71737187
New: "3",
71747188
},
7189+
{
7190+
Type: DiffTypeAdded,
7191+
Name: "Uid",
7192+
Old: "",
7193+
New: "1002",
7194+
},
71757195
{
71767196
Type: DiffTypeAdded,
71777197
Name: "VaultGrace",
@@ -7234,6 +7254,12 @@ func TestTaskDiff(t *testing.T) {
72347254
Old: "true",
72357255
New: "",
72367256
},
7257+
{
7258+
Type: DiffTypeDeleted,
7259+
Name: "Gid",
7260+
Old: "20",
7261+
New: "",
7262+
},
72377263
{
72387264
Type: DiffTypeDeleted,
72397265
Name: "Perms",
@@ -7252,6 +7278,12 @@ func TestTaskDiff(t *testing.T) {
72527278
Old: "2",
72537279
New: "",
72547280
},
7281+
{
7282+
Type: DiffTypeDeleted,
7283+
Name: "Uid",
7284+
Old: "1000",
7285+
New: "",
7286+
},
72557287
{
72567288
Type: DiffTypeDeleted,
72577289
Name: "VaultGrace",

nomad/structs/structs.go

+3
Original file line numberDiff line numberDiff line change
@@ -7711,6 +7711,9 @@ type Template struct {
77117711

77127712
// Perms is the permission the file should be written out with.
77137713
Perms string
7714+
// User and group that should own the file.
7715+
Uid int
7716+
Gid int
77147717

77157718
// LeftDelim and RightDelim are optional configurations to control what
77167719
// delimiter is utilized when parsing the template.

website/content/api-docs/json-jobs.mdx

+14
Original file line numberDiff line numberDiff line change
@@ -1077,6 +1077,20 @@ README][ct].
10771077
- `Perms` - Specifies the rendered template's permissions. File permissions are
10781078
given as octal of the Unix file permissions `rwxrwxrwx`.
10791079

1080+
- `Uid` - Specifies the rendered template owner's user ID.
1081+
1082+
~> **Caveat:** Works only on Unix-based systems. Be careful when using
1083+
containerized drivers, suck as `docker` or `podman`, as groups and users
1084+
inside the container may have different IDs than on the host system. This
1085+
feature will also **not** work with Docker Desktop.
1086+
1087+
- `Gid` - Specifies the rendered template owner's group ID.
1088+
1089+
~> **Caveat:** Works only on Unix-based systems. Be careful when using
1090+
containerized drivers, suck as `docker` or `podman`, as groups and users
1091+
inside the container may have different IDs than on the host system. This
1092+
feature will also **not** work with Docker Desktop.
1093+
10801094
- `RightDelim` - Specifies the right delimiter to use in the template. The default
10811095
is "}}" for some templates, it may be easier to use a different delimiter that
10821096
does not conflict with the output file itself.

0 commit comments

Comments
 (0)