@@ -23,6 +23,8 @@ import (
23
23
"io"
24
24
"os/exec"
25
25
"path"
26
+ "strconv"
27
+ "strings"
26
28
"sync"
27
29
"time"
28
30
@@ -35,6 +37,10 @@ import (
35
37
"k8s.io/minikube/pkg/util"
36
38
)
37
39
40
+ var (
41
+ layout = "2006-01-02 15:04:05.999999999 -0700"
42
+ )
43
+
38
44
// SSHRunner runs commands through SSH.
39
45
//
40
46
// It implements the CommandRunner interface.
@@ -143,6 +149,15 @@ func (s *SSHRunner) RunCmd(cmd *exec.Cmd) (*RunResult, error) {
143
149
144
150
// Copy copies a file to the remote over SSH.
145
151
func (s * SSHRunner ) Copy (f assets.CopyableFile ) error {
152
+ dst := path .Join (path .Join (f .GetTargetDir (), f .GetTargetName ()))
153
+ exists , err := s .sameFileExists (f , dst )
154
+ if err != nil {
155
+ glog .Infof ("Checked if %s exists, but got error: %v" , dst , err )
156
+ }
157
+ if exists {
158
+ glog .Infof ("Skipping copying %s as it already exists" , dst )
159
+ return nil
160
+ }
146
161
sess , err := s .c .NewSession ()
147
162
if err != nil {
148
163
return errors .Wrap (err , "NewSession" )
@@ -156,7 +171,6 @@ func (s *SSHRunner) Copy(f assets.CopyableFile) error {
156
171
// StdinPipe is closed. But let's use errgroup to make it explicit.
157
172
var g errgroup.Group
158
173
var copied int64
159
- dst := path .Join (path .Join (f .GetTargetDir (), f .GetTargetName ()))
160
174
glog .Infof ("Transferring %d bytes to %s" , f .GetLength (), dst )
161
175
162
176
g .Go (func () error {
@@ -182,13 +196,59 @@ func (s *SSHRunner) Copy(f assets.CopyableFile) error {
182
196
})
183
197
184
198
scp := fmt .Sprintf ("sudo mkdir -p %s && sudo scp -t %s" , f .GetTargetDir (), f .GetTargetDir ())
199
+ mtime , err := f .GetModTime ()
200
+ if err != nil {
201
+ glog .Infof ("error getting modtime for %s: %v" , dst , err )
202
+ } else {
203
+ scp += fmt .Sprintf (" && sudo touch -d \" %s\" %s" , mtime .Format (layout ), dst )
204
+ }
185
205
out , err := sess .CombinedOutput (scp )
186
206
if err != nil {
187
207
return fmt .Errorf ("%s: %s\n output: %s" , scp , err , out )
188
208
}
189
209
return g .Wait ()
190
210
}
191
211
212
+ func (s * SSHRunner ) sameFileExists (f assets.CopyableFile , dst string ) (bool , error ) {
213
+ // get file size and modtime of the source
214
+ srcSize := f .GetLength ()
215
+ srcModTime , err := f .GetModTime ()
216
+ if err != nil {
217
+ return false , err
218
+ }
219
+ if srcModTime .IsZero () {
220
+ return false , nil
221
+ }
222
+
223
+ // get file size and modtime of the destination
224
+ sess , err := s .c .NewSession ()
225
+ if err != nil {
226
+ return false , err
227
+ }
228
+
229
+ cmd := "stat -c \" %s %y\" " + dst
230
+ out , err := sess .CombinedOutput (cmd )
231
+ if err != nil {
232
+ return false , err
233
+ }
234
+ outputs := strings .SplitN (strings .Trim (string (out ), "\n " ), " " , 2 )
235
+
236
+ dstSize , err := strconv .Atoi (outputs [0 ])
237
+ if err != nil {
238
+ return false , err
239
+ }
240
+ dstModTime , err := time .Parse (layout , outputs [1 ])
241
+ if err != nil {
242
+ return false , err
243
+ }
244
+
245
+ // compare sizes and modtimes
246
+ if srcSize != dstSize {
247
+ return false , errors .New ("source file and destination file are different sizes" )
248
+ }
249
+ return srcModTime .Equal (dstModTime ), nil
250
+ }
251
+
192
252
// teePrefix copies bytes from a reader to writer, logging each new line.
193
253
func teePrefix (prefix string , r io.Reader , w io.Writer , logger func (format string , args ... interface {})) error {
194
254
scanner := bufio .NewScanner (r )
0 commit comments