@@ -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+
57135func 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
135227func 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.
154246func 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 {
0 commit comments