Skip to content

Commit 5697be8

Browse files
authored
Merge pull request #12 from FortnoxAB/feature/user-group
Set user and group on a file
2 parents fd997ea + df7b69b commit 5697be8

File tree

7 files changed

+466
-43
lines changed

7 files changed

+466
-43
lines changed

cmd/gmc/main.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,6 @@ func app() *cli.App {
9393
Name: "hostname",
9494
Usage: "Set hostname instead of checking hostname with os.Hostname()",
9595
},
96-
// masters fetch and push over websocket
97-
// &cli.DurationFlag{
98-
// Name: "poll-interval",
99-
// Value: time.Minute,
100-
// Usage: "How often do we sync from the masters.",
101-
// },
10296
},
10397
},
10498
{
@@ -265,6 +259,11 @@ func app() *cli.App {
265259
Value: "/usr/local/bin",
266260
Usage: "where to put the gmc binary when bootstrapping",
267261
},
262+
&cli.StringFlag{
263+
Name: "zone",
264+
Value: "",
265+
Usage: "which zone to connect to from the agent when bootstrapping",
266+
},
268267
&cli.StringFlag{
269268
Name: "ssh-user",
270269
Value: "",

pkg/admin/admin.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type Admin struct {
4141
regexp string
4242
dry bool
4343

44+
zone string
45+
4446
// binary location when Bootstrap
4547
targetPath string
4648
sshUser string
@@ -56,6 +58,7 @@ func NewAdminFromContext(c *cli.Context) *Admin {
5658
dry: c.Bool("dry"),
5759
targetPath: c.String("target-path"),
5860
sshUser: c.String("ssh-user"),
61+
zone: c.String("zone"),
5962
}
6063
}
6164

pkg/admin/bootstrap.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func (a *Admin) Bootstrap(ctx context.Context, hosts []string) error {
5757
},
5858
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
5959
}
60-
err := sshCommands(ctx, config, host, binaryPath, a.targetPath, master)
60+
err := sshCommands(ctx, config, host, binaryPath, a.targetPath, master, a.zone)
6161
if err != nil {
6262
return err
6363
}
@@ -66,7 +66,7 @@ func (a *Admin) Bootstrap(ctx context.Context, hosts []string) error {
6666
return nil
6767
}
6868

69-
func sshCommands(ctx context.Context, config *ssh.ClientConfig, host, src, dstFolder, master string) error {
69+
func sshCommands(ctx context.Context, config *ssh.ClientConfig, host, src, dstFolder, master, zone string) error {
7070
client, err := ssh.Dial("tcp", host+":22", config)
7171
if err != nil {
7272
return err
@@ -109,7 +109,7 @@ func sshCommands(ctx context.Context, config *ssh.ClientConfig, host, src, dstFo
109109

110110
// TODO start goroutine here that checks for the server to appear in API and need accept! then accept
111111

112-
err = runOverSSH(ctx, client, fmt.Sprintf("sudo %s agent --one-shot --master %s", dst, master))
112+
err = runOverSSH(ctx, client, fmt.Sprintf("sudo %s agent --one-shot --master %s --zone %s", dst, master, zone))
113113
if err != nil {
114114
return err
115115
}

pkg/agent/reconciliation/files.go

Lines changed: 149 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ import (
1414
"io"
1515
"net/http"
1616
"os"
17+
"os/user"
1718
"path/filepath"
19+
"strconv"
20+
"syscall"
1821
"time"
1922

2023
"github.com/fortnoxab/gitmachinecontroller/pkg/api/v1/types"
@@ -54,36 +57,124 @@ func (mr *MachineReconciler) files(files types.Files) {
5457
}
5558
}
5659

60+
// assertSameOwner returns a bool if it was changed
61+
func assertSameOwner(file os.FileInfo, fileSpec *types.File) (bool, error) {
62+
if fileSpec.User == "" && fileSpec.Group == "" {
63+
return false, nil
64+
}
65+
var fileUid string
66+
var fileGid string
67+
stat, ok := file.Sys().(*syscall.Stat_t)
68+
if !ok {
69+
return false, fmt.Errorf("not syscall.Stat_t")
70+
}
71+
fileUid = strconv.Itoa(int(stat.Uid))
72+
fileGid = strconv.Itoa(int(stat.Gid))
73+
u, err := user.Lookup(fileSpec.User)
74+
if err != nil {
75+
return false, err
76+
}
77+
g, err := user.LookupGroup(fileSpec.User)
78+
if err != nil {
79+
return false, err
80+
}
81+
82+
if fileUid == u.Uid && fileGid == g.Gid {
83+
return false, nil
84+
}
85+
86+
return true, os.Chown(file.Name(), int(stat.Uid), int(stat.Gid))
87+
}
88+
func chown(file *os.File, userName, group string) error {
89+
if userName == "" && group == "" {
90+
return nil
91+
}
92+
u, err := user.Lookup(userName)
93+
if err != nil {
94+
return err
95+
}
96+
g, err := user.LookupGroup(group)
97+
if err != nil {
98+
return err
99+
}
100+
101+
newUid, err := strconv.Atoi(u.Uid)
102+
if err != nil {
103+
return err
104+
}
105+
newGid, err := strconv.Atoi(g.Gid)
106+
if err != nil {
107+
return err
108+
}
109+
return file.Chown(newUid, newGid)
110+
}
111+
func chownName(file, userName, group string) error {
112+
if userName == "" && group == "" {
113+
return nil
114+
}
115+
u, err := user.Lookup(userName)
116+
if err != nil {
117+
return err
118+
}
119+
g, err := user.LookupGroup(group)
120+
if err != nil {
121+
return err
122+
}
123+
124+
newUid, err := strconv.Atoi(u.Uid)
125+
if err != nil {
126+
return err
127+
}
128+
newGid, err := strconv.Atoi(g.Gid)
129+
if err != nil {
130+
return err
131+
}
132+
return os.Chown(file, newUid, newGid)
133+
}
134+
57135
func writeContentIfNeeded(file *types.File) (bool, error) {
58-
mode, err := file.FileMode()
136+
newMode, err := file.FileMode()
59137
if err != nil {
60138
return false, err
61139
}
140+
62141
statedFile, err := os.Stat(file.Path)
63142
if errors.Is(err, os.ErrNotExist) { // New file we can write directly to desired location
64-
err = os.WriteFile(file.Path, []byte(file.Content), mode)
143+
err = os.WriteFile(file.Path, []byte(file.Content), newMode)
65144
if err != nil {
66145
return false, err
67146
}
147+
err = chownName(file.Path, file.User, file.Group)
148+
if err != nil {
149+
return true, err
150+
}
68151
return true, nil
69152
}
70153

71-
equal := true
72-
if int64(len(file.Content)) != statedFile.Size() {
154+
equal := false
155+
if int64(len(file.Content)) == statedFile.Size() { // we only need to do expensive fileEqual if size are the same
73156
equal, err = fileEqual(file.Content, file.Path)
74157
if err != nil {
75158
return false, err
76159
}
77160
}
78161
if equal {
79-
if mode != statedFile.Mode() { // check if content was same but we need to update mode
80-
err = os.Chmod(file.Path, mode)
162+
var changedMode bool
163+
if newMode != statedFile.Mode() { // check if content was same but we need to update newMode
164+
err = os.Chmod(file.Path, newMode)
81165
if err != nil {
82166
return false, err
83167
}
168+
changedMode = true
84169
}
170+
var changedOwner bool
171+
changedOwner, err = assertSameOwner(statedFile, file)
172+
if err != nil {
173+
return false, err
174+
}
175+
85176
logrus.Debug(file.Path, " already equal")
86-
return false, nil
177+
return changedOwner || changedMode, nil
87178
}
88179

89180
// existing file we make a tempfile in the same target directory and then atomic move.
@@ -92,8 +183,12 @@ func writeContentIfNeeded(file *types.File) (bool, error) {
92183
return false, err
93184
}
94185
defer tempFile.Close()
95-
err = tempFile.Chmod(mode)
186+
err = tempFile.Chmod(newMode)
187+
if err != nil {
188+
return false, err
189+
}
96190

191+
err = chown(tempFile, file.User, file.Group)
97192
if err != nil {
98193
return false, err
99194
}
@@ -126,10 +221,7 @@ func needsFetch(file *types.File) (bool, error) {
126221
return false, err
127222
}
128223

129-
if !equal {
130-
return true, nil // checksum mismatch file needs fetching
131-
}
132-
return false, nil
224+
return !equal, nil
133225
}
134226

135227
func hashIsEqual(r io.Reader, checksum string) (bool, error) {
@@ -152,17 +244,42 @@ func hashIsEqual(r io.Reader, checksum string) (bool, error) {
152244

153245
// fetchFromURL returns changed bool and an error.
154246
func fetchFromURL(file *types.File) (bool, error) {
155-
missing, err := needsFetch(file)
247+
shouldFetch, err := needsFetch(file)
156248
if err != nil {
157249
return false, err
158250
}
159251

160-
// exit early if checksum already is correct.
161-
if !missing {
162-
return false, nil
252+
// exit early if checksum already is correct. But assert chmod and chown
253+
if !shouldFetch {
254+
var newMode os.FileMode
255+
newMode, err = file.FileMode()
256+
if err != nil {
257+
return false, err
258+
}
259+
260+
var statedFile os.FileInfo
261+
statedFile, err = os.Stat(file.Path)
262+
if err != nil {
263+
return false, err
264+
}
265+
266+
var changedMode bool
267+
var changedOwner bool
268+
if newMode != statedFile.Mode() { // check if content was same but we need to update newMode
269+
err = os.Chmod(file.Path, newMode)
270+
if err != nil {
271+
return false, err
272+
}
273+
changedMode = true
274+
}
275+
changedOwner, err = assertSameOwner(statedFile, file)
276+
if err != nil {
277+
return false, err
278+
}
279+
return changedMode || changedOwner, nil
163280
}
164281

165-
mode, err := file.FileMode()
282+
newMode, err := file.FileMode()
166283
if err != nil {
167284
return false, err
168285
}
@@ -184,10 +301,9 @@ func fetchFromURL(file *types.File) (bool, error) {
184301
if err != nil {
185302
return false, err
186303
}
304+
defer os.Remove(tempFile.Name())
187305
defer tempFile.Close()
188306

189-
tempFile.Chmod(mode)
190-
191307
_, err = io.Copy(tempFile, resp.Body)
192308
if err != nil {
193309
return false, err
@@ -199,23 +315,16 @@ func fetchFromURL(file *types.File) (bool, error) {
199315
if err != nil {
200316
return false, err
201317
}
318+
defer os.Remove(newTempFile.Name())
202319
defer newTempFile.Close()
203-
newTempFile.Chmod(mode)
204320

205321
err = extractTarGz(tempFile, newTempFile, file.ExtractFile)
206322
if err != nil {
207323
return false, err
208324
}
209325

210-
newTempFile.Seek(0, io.SeekStart)
211-
equal, err := hashIsEqual(newTempFile, file.Checksum)
212-
if err != nil {
213-
return false, err
214-
}
215-
if !equal {
216-
return false, fmt.Errorf("checksum mismatch. expected file to be %s", file.Checksum)
217-
}
218-
return true, os.Rename(newTempFile.Name(), file.Path)
326+
tempFile.Close()
327+
tempFile = newTempFile
219328
}
220329

221330
tempFile.Seek(0, io.SeekStart)
@@ -226,6 +335,15 @@ func fetchFromURL(file *types.File) (bool, error) {
226335
if !equal {
227336
return false, fmt.Errorf("checksum mismatch. expected file to be %s", file.Checksum)
228337
}
338+
err = tempFile.Chmod(newMode)
339+
if err != nil {
340+
return false, err
341+
}
342+
err = chown(tempFile, file.User, file.Group)
343+
if err != nil {
344+
return false, err
345+
}
346+
tempFile.Close() // close so we can move it
229347
return true, os.Rename(tempFile.Name(), file.Path)
230348
}
231349

@@ -249,12 +367,12 @@ func extractTarGz(r io.Reader, w io.Writer, singleFile string) error {
249367
}
250368

251369
switch header.Typeflag {
252-
case tar.TypeDir:
370+
case tar.TypeDir: //TODO support entire folder extracts?
253371
// if err := os.Mkdir(header.Name, 0755); err != nil {
254372
// log.Fatalf("ExtractTarGz: Mkdir() failed: %s", err.Error())
255373
// }
256374
case tar.TypeReg:
257-
if header.Name != singleFile {
375+
if filepath.Clean(header.Name) != singleFile {
258376
continue
259377
}
260378
if _, err := io.Copy(w, tarReader); err != nil {

pkg/agent/reconciliation/reconciliation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func (mr *MachineReconciler) unitNeedsTrigger(systemd *types.SystemdReference) {
7979
}
8080

8181
func (mr *MachineReconciler) runSystemdTriggers() error {
82-
if mr.daemonReloadNeeded {
82+
if mr.daemonReloadNeeded && len(mr.restartUnits) > 0 {
8383
_, _, err := mr.commander.Run("systemctl daemon reload")
8484
if err != nil {
8585
return err

0 commit comments

Comments
 (0)