1 package sync
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9
10 "github.com/devfile/library/v2/pkg/devfile/generator"
11 dfutil "github.com/devfile/library/v2/pkg/util"
12 gitignore "github.com/sabhiram/go-gitignore"
13
14 "github.com/redhat-developer/odo/pkg/exec"
15 "github.com/redhat-developer/odo/pkg/platform"
16 "github.com/redhat-developer/odo/pkg/util"
17
18 "k8s.io/klog"
19 )
20
21
22 type SyncClient struct {
23 platformClient platform.Client
24 execClient exec.Client
25 }
26
27 var _ Client = (*SyncClient)(nil)
28
29
30 func NewSyncClient(platformClient platform.Client, execClient exec.Client) *SyncClient {
31 return &SyncClient{
32 platformClient: platformClient,
33 execClient: execClient,
34 }
35 }
36
37
38
39
40
41
42 func (a SyncClient) SyncFiles(ctx context.Context, syncParameters SyncParameters) (bool, error) {
43
44
45 forceWrite := false
46
47
48 var ret util.IndexerRet
49
50 var deletedFiles []string
51 var changedFiles []string
52 isWatch := len(syncParameters.WatchFiles) > 0 || len(syncParameters.WatchDeletedFiles) > 0
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71 indexRegeneratedByWatch := false
72
73
74
75 if isWatch && !syncParameters.DevfileScanIndexForWatch {
76
77 err := updateIndexWithWatchChanges(syncParameters)
78
79 if err != nil {
80 return false, err
81 }
82
83 changedFiles = syncParameters.WatchFiles
84 deletedFiles = syncParameters.WatchDeletedFiles
85 deletedFiles, err = dfutil.RemoveRelativePathFromFiles(deletedFiles, syncParameters.Path)
86 if err != nil {
87 return false, fmt.Errorf("unable to remove relative path from list of changed/deleted files: %w", err)
88 }
89 indexRegeneratedByWatch = true
90
91 }
92
93 if !indexRegeneratedByWatch {
94
95
96
97
98
99 odoFolder := filepath.Join(syncParameters.Path, ".odo")
100 if _, err := os.Stat(odoFolder); os.IsNotExist(err) {
101 err = os.Mkdir(odoFolder, 0750)
102 if err != nil {
103 return false, fmt.Errorf("unable to create directory: %w", err)
104 }
105 }
106
107
108
109
110 if syncParameters.ForcePush {
111 err := util.DeleteIndexFile(syncParameters.Path)
112 if err != nil {
113 return false, fmt.Errorf("unable to reset the index file: %w", err)
114 }
115 }
116
117
118 var err error
119 ret, err = util.RunIndexerWithRemote(syncParameters.Path, syncParameters.IgnoredFiles, syncParameters.Files)
120
121 if err != nil {
122 return false, fmt.Errorf("unable to run indexer: %w", err)
123 }
124
125 if len(ret.FilesChanged) > 0 || len(ret.FilesDeleted) > 0 {
126 forceWrite = true
127 }
128
129
130
131 filesChangedFiltered, filesDeletedFiltered, err := filterIgnores(syncParameters.Path, ret.FilesChanged, ret.FilesDeleted, syncParameters.IgnoredFiles)
132 if err != nil {
133 return false, err
134 }
135
136 deletedFiles = append(filesDeletedFiltered, ret.RemoteDeleted...)
137 deletedFiles = append(deletedFiles, ret.RemoteDeleted...)
138 klog.V(4).Infof("List of files to be deleted: +%v", deletedFiles)
139 changedFiles = filesChangedFiltered
140 klog.V(4).Infof("List of files changed: +%v", changedFiles)
141
142 if len(filesChangedFiltered) == 0 && len(filesDeletedFiltered) == 0 && !syncParameters.ForcePush {
143 return false, nil
144 }
145
146 if syncParameters.ForcePush {
147 deletedFiles = append(deletedFiles, "*")
148 }
149 }
150
151 err := a.pushLocal(ctx, syncParameters.Path, changedFiles, deletedFiles, syncParameters.ForcePush, syncParameters.IgnoredFiles, syncParameters.CompInfo, ret)
152 if err != nil {
153 return false, fmt.Errorf("failed to sync to component with name %s: %w", syncParameters.CompInfo.ComponentName, err)
154 }
155 if forceWrite {
156 err = util.WriteFile(ret.NewFileMap, ret.ResolvedPath)
157 if err != nil {
158 return false, fmt.Errorf("failed to write file: %w", err)
159 }
160 }
161
162 return true, nil
163 }
164
165
166
167 func filterIgnores(path string, filesChanged, filesDeleted, absIgnoreRules []string) (filesChangedFiltered, filesDeletedFiltered []string, err error) {
168 ignoreMatcher := gitignore.CompileIgnoreLines(absIgnoreRules...)
169 for _, file := range filesChanged {
170
171 rel, err := filepath.Rel(path, file)
172 if err != nil {
173 return nil, nil, fmt.Errorf("path=%q, file=%q, %w", path, file, err)
174 }
175 match := ignoreMatcher.MatchesPath(rel)
176 if !match {
177 filesChangedFiltered = append(filesChangedFiltered, file)
178 }
179 }
180
181 for _, file := range filesDeleted {
182
183 match := ignoreMatcher.MatchesPath(file)
184 if !match {
185 filesDeletedFiltered = append(filesDeletedFiltered, file)
186 }
187 }
188 return filesChangedFiltered, filesDeletedFiltered, nil
189 }
190
191
192 func (a SyncClient) pushLocal(ctx context.Context, path string, files []string, delFiles []string, isForcePush bool, globExps []string, compInfo ComponentInfo, ret util.IndexerRet) error {
193 klog.V(4).Infof("Push: componentName: %s, path: %s, files: %s, delFiles: %s, isForcePush: %+v", compInfo.ComponentName, path, files, delFiles, isForcePush)
194
195
196 emptyDir, err := dfutil.IsEmpty(path)
197 if err != nil {
198 return fmt.Errorf("unable to check directory: %s: %w", path, err)
199 } else if emptyDir {
200 return fmt.Errorf("directory/file %s is empty", path)
201 }
202
203
204 syncFolder := compInfo.SyncFolder
205
206 if syncFolder != generator.DevfileSourceVolumeMount {
207
208 klog.V(4).Infof("Creating %s on the remote container if it doesn't already exist", syncFolder)
209 cmdArr := getCmdToCreateSyncFolder(syncFolder)
210
211 _, _, err = a.execClient.ExecuteCommand(ctx, cmdArr, compInfo.PodName, compInfo.ContainerName, false, nil, nil)
212 if err != nil {
213 return err
214 }
215 }
216
217 if len(delFiles) > 0 {
218 cmdArr := getCmdToDeleteFiles(delFiles, syncFolder)
219
220 _, _, err = a.execClient.ExecuteCommand(ctx, cmdArr, compInfo.PodName, compInfo.ContainerName, false, nil, nil)
221 if err != nil {
222 return err
223 }
224 }
225
226 if !isForcePush {
227 if len(files) == 0 && len(delFiles) == 0 {
228 return nil
229 }
230 }
231
232 if isForcePush || len(files) > 0 {
233 klog.V(4).Infof("Copying files %s to pod", strings.Join(files, " "))
234 err = a.CopyFile(ctx, path, compInfo, syncFolder, files, globExps, ret)
235 if err != nil {
236 return fmt.Errorf("unable push files to pod: %w", err)
237 }
238 }
239
240 return nil
241 }
242
243
244
245 func updateIndexWithWatchChanges(syncParameters SyncParameters) error {
246 indexFilePath, err := util.ResolveIndexFilePath(syncParameters.Path)
247
248 if err != nil {
249 return fmt.Errorf("unable to resolve path: %s: %w", syncParameters.Path, err)
250 }
251
252
253 _, err = os.Stat(indexFilePath)
254 if err != nil {
255
256
257
258
259
260
261 return fmt.Errorf("resolved path doesn't exist: %s: %w", indexFilePath, err)
262 }
263
264
265 fileIndex, err := util.ReadFileIndex(indexFilePath)
266 if err != nil {
267 return fmt.Errorf("unable to read index from path: %s: %w", indexFilePath, err)
268 }
269
270 rootDir := syncParameters.Path
271
272
273 for _, deletedFile := range syncParameters.WatchDeletedFiles {
274
275 relativePath, err := util.CalculateFileDataKeyFromPath(deletedFile, rootDir)
276
277 if err != nil {
278 klog.V(4).Infof("Error occurred for %s: %v", deletedFile, err)
279 continue
280 }
281 delete(fileIndex.Files, relativePath)
282 klog.V(4).Infof("Removing watch deleted file from index: %s", relativePath)
283 }
284
285
286 for _, addedOrModifiedFile := range syncParameters.WatchFiles {
287 relativePath, fileData, err := util.GenerateNewFileDataEntry(addedOrModifiedFile, rootDir)
288
289 if err != nil {
290 klog.V(4).Infof("Error occurred for %s: %v", addedOrModifiedFile, err)
291 continue
292 }
293 fileIndex.Files[relativePath] = *fileData
294 klog.V(4).Infof("Added/updated watched file in index: %s", relativePath)
295 }
296
297
298 return util.WriteFile(fileIndex.Files, indexFilePath)
299
300 }
301
302
303 func getCmdToCreateSyncFolder(syncFolder string) []string {
304 return []string{"mkdir", "-p", syncFolder}
305 }
306
307
308 func getCmdToDeleteFiles(delFiles []string, syncFolder string) []string {
309 rmPaths := dfutil.GetRemoteFilesMarkedForDeletion(delFiles, syncFolder)
310 klog.V(4).Infof("remote files marked for deletion are %+v", rmPaths)
311 cmdArr := []string{"rm", "-rf"}
312
313 for _, remote := range rmPaths {
314 cmdArr = append(cmdArr, filepath.ToSlash(remote))
315 }
316 return cmdArr
317 }
318
View as plain text