Skip to content

Commit

Permalink
initial sync files that modified after image creation
Browse files Browse the repository at this point in the history
Signed-off-by: Joana Hrotko <[email protected]>
  • Loading branch information
jhrotko authored and glours committed Aug 9, 2024
1 parent 485c0eb commit 9c03797
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 24 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/Microsoft/go-winio v0.6.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/buger/goterm v1.0.4
github.com/compose-spec/compose-go/v2 v2.1.5
github.com/compose-spec/compose-go/v2 v2.1.6
github.com/containerd/containerd v1.7.20
github.com/containerd/platforms v0.2.1
github.com/davecgh/go-spew v1.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+g
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/compose-spec/compose-go/v2 v2.1.5 h1:6YoC9ik3NXdSYtgRn51EMZ2DxzGPyGjZ8M0B7mXTXeQ=
github.com/compose-spec/compose-go/v2 v2.1.5/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
github.com/compose-spec/compose-go/v2 v2.1.6 h1:d0Cs0DffmOwmSzs0YPHwKCskknGq2jfGg4uGowlEpps=
github.com/compose-spec/compose-go/v2 v2.1.6/go.mod h1:lFN0DrMxIncJGYAXTfWuajfwj5haBJqrBkarHcnjJKc=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
Expand Down
101 changes: 80 additions & 21 deletions pkg/compose/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ func (s *composeService) watch(ctx context.Context, syncChannel chan bool, proje
success, err := trigger.Extensions.Get("x-initialSync", &initialSync)
if err == nil && success && initialSync && (trigger.Action == types.WatchActionSync || trigger.Action == types.WatchActionSyncRestart) {
// Need to check initial files are in container that are meant to be synched from watch action
s.initialSync(ctx, service, trigger, ignore, syncer)
err := s.initialSync(ctx, project, service, trigger, ignore, syncer)
if err != nil {
return err
}
}
}
paths = append(paths, trigger.Path)
Expand Down Expand Up @@ -583,29 +586,43 @@ func (s *composeService) pruneDanglingImagesOnRebuild(ctx context.Context, proje
}
}

func (s *composeService) initialSync(ctx context.Context, service types.ServiceConfig, trigger types.Trigger, ignore watch.PathMatcher, syncer sync.Syncer) error {
dockerFileIgnore, _ := watch.NewDockerPatternMatcher("/", []string{"Dockerfile", "*compose*.y*ml"})
triggerIgnore, _ := watch.NewDockerPatternMatcher("/", trigger.Ignore)
// Walks develop.watch.path and checks which files should be copied inside the container
// ignores develop.watch.ignore, Dockerfile, compose files, bind mounted paths and .git
func (s *composeService) initialSync(ctx context.Context, project *types.Project, service types.ServiceConfig, trigger types.Trigger, ignore watch.PathMatcher, syncer sync.Syncer) error {
dockerFileIgnore, err := watch.NewDockerPatternMatcher("/", []string{"Dockerfile", "*compose*.y*ml"})
if err != nil {
return err
}
triggerIgnore, err := watch.NewDockerPatternMatcher("/", trigger.Ignore)
if err != nil {
return err
}
ignoreInitialSync := watch.NewCompositeMatcher(ignore, dockerFileIgnore, triggerIgnore)

pathsToCopy, err := initialSyncFiles(service, trigger, ignoreInitialSync)
pathsToCopy, err := s.initialSyncFiles(ctx, project, service, trigger, ignoreInitialSync)
if err != nil {
return err
}

return syncer.Sync(ctx, service, pathsToCopy)
}

func initialSyncFiles(service types.ServiceConfig, trigger types.Trigger, ignore watch.PathMatcher) ([]sync.PathMapping, error) {
// Syncs files from develop.watch.path if thy have been modified after the image has been created
//
//nolint:gocyclo
func (s *composeService) initialSyncFiles(ctx context.Context, project *types.Project, service types.ServiceConfig, trigger types.Trigger, ignore watch.PathMatcher) ([]sync.PathMapping, error) {
fi, err := os.Stat(trigger.Path)
if err != nil {
return nil, err
}

timeImageCreated, err := s.imageCreatedTime(ctx, project, service.Name)
if err != nil {
return nil, err
}
var pathsToCopy []sync.PathMapping
switch mode := fi.Mode(); {
case mode.IsDir():
// do directory stuff
// process directory
err = filepath.WalkDir(trigger.Path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
// handle possible path err, just in case...
Expand All @@ -615,34 +632,76 @@ func initialSyncFiles(service types.ServiceConfig, trigger types.Trigger, ignore
// walk starts at the root directory
return nil
}
rel, _ := filepath.Rel(trigger.Path, path)
if shouldIgnoreOrSkip(filepath.Base(path), ignore) || checkIfPathAlreadyBindMounted(path, service.Volumes) {
if shouldIgnore(filepath.Base(path), ignore) || checkIfPathAlreadyBindMounted(path, service.Volumes) {
// By definition sync ignores bind mounted paths
if d.IsDir() {
return fs.SkipDir // ignore or skip folder
// skip folder
return fs.SkipDir
}
return nil // ignore or skip file
return nil // skip file
}
info, err := d.Info()
if err != nil {
return err
}
if !d.IsDir() {
if info.ModTime().Before(timeImageCreated) {
// skip file if it was modified before image creation
return nil
}
rel, err := filepath.Rel(trigger.Path, path)
if err != nil {
return err
}
// only copy files (and not full directories)
pathsToCopy = append(pathsToCopy, sync.PathMapping{
HostPath: path,
ContainerPath: filepath.Join(trigger.Target, rel),
})
}
pathsToCopy = append(pathsToCopy, sync.PathMapping{
HostPath: path,
ContainerPath: filepath.Join(trigger.Target, rel),
})
return nil
})
case mode.IsRegular():
// do file stuff
if !shouldIgnoreOrSkip(filepath.Base(trigger.Path), ignore) && !checkIfPathAlreadyBindMounted(trigger.Path, service.Volumes) {
// process file
if fi.ModTime().After(timeImageCreated) && !shouldIgnore(filepath.Base(trigger.Path), ignore) && !checkIfPathAlreadyBindMounted(trigger.Path, service.Volumes) {
pathsToCopy = append(pathsToCopy, sync.PathMapping{
HostPath: trigger.Path,
ContainerPath: trigger.Target,
})
}
}
return pathsToCopy, nil
return pathsToCopy, err
}

func shouldIgnoreOrSkip(rel string, ignore watch.PathMatcher) bool {
shouldIgnore, _ := ignore.Matches(rel)
func shouldIgnore(name string, ignore watch.PathMatcher) bool {
shouldIgnore, _ := ignore.Matches(name)
// ignore files that match any ignore pattern
return shouldIgnore
}

// gets the image creation time for a service
func (s *composeService) imageCreatedTime(ctx context.Context, project *types.Project, serviceName string) (time.Time, error) {
containers, err := s.apiClient().ContainerList(ctx, container.ListOptions{
All: true,
Filters: filters.NewArgs(
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, project.Name)),
filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, serviceName))),
})
if err != nil {
return time.Now(), err
}
if len(containers) == 0 {
return time.Now(), fmt.Errorf("Could not get created time for service's image")
}

img, _, err := s.apiClient().ImageInspectWithRaw(ctx, containers[0].ImageID)
if err != nil {
return time.Now(), err
}
// Need to get oldest one?
timeCreated, err := time.Parse(time.RFC3339Nano, img.Created)
if err != nil {
return time.Now(), err
}
return timeCreated, nil
}

0 comments on commit 9c03797

Please sign in to comment.