Skip to content

Commit 19f6691

Browse files
milasndeloof
authored andcommitted
watch: only allow a single instance per-project
This is a good place to start introducing (local) exclusivity to Compose. Now, when `alpha watch` launches, it will check for the existence of a PID file in the user XDG runtime directory, and create one if the existing one is stale or does not exist. If the PID file exists and is valid, an error is returned and Compose exits. A slight tweak to the experimental remote Git loader has been made to use the XDG package for consistency. Signed-off-by: Milas Bowman <[email protected]>
1 parent 186744e commit 19f6691

File tree

6 files changed

+68
-12
lines changed

6 files changed

+68
-12
lines changed

cmd/compose/watch.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"fmt"
2222
"os"
2323

24+
"github.com/docker/compose/v2/internal/locker"
25+
2426
"github.com/docker/compose/v2/pkg/api"
2527
"github.com/spf13/cobra"
2628
)
@@ -57,5 +59,13 @@ func runWatch(ctx context.Context, backend api.Service, opts watchOptions, servi
5759
return err
5860
}
5961

62+
l, err := locker.NewPidfile(project.Name)
63+
if err != nil {
64+
return fmt.Errorf("cannot take exclusive lock for project %q: %v", project.Name, err)
65+
}
66+
if err := l.Lock(); err != nil {
67+
return fmt.Errorf("cannot take exclusive lock for project %q: %v", project.Name, err)
68+
}
69+
6070
return backend.Watch(ctx, project, services, api.WatchOptions{})
6171
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.21
55
require (
66
github.com/AlecAivazis/survey/v2 v2.3.7
77
github.com/Microsoft/go-winio v0.6.1
8+
github.com/adrg/xdg v0.4.0
89
github.com/buger/goterm v1.0.4
910
github.com/compose-spec/compose-go v1.18.2
1011
github.com/containerd/console v1.0.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/O
6666
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
6767
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
6868
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
69+
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
70+
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
6971
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
7072
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
7173
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=

internal/locker/pidfile.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
Copyright 2023 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package locker
18+
19+
import (
20+
"fmt"
21+
"os"
22+
23+
"github.com/adrg/xdg"
24+
"github.com/docker/docker/pkg/pidfile"
25+
)
26+
27+
type Pidfile struct {
28+
path string
29+
}
30+
31+
func NewPidfile(projectName string) (*Pidfile, error) {
32+
path, err := xdg.RuntimeFile(fmt.Sprintf("docker-compose.%s.pid", projectName))
33+
if err != nil {
34+
return nil, err
35+
}
36+
return &Pidfile{path: path}, nil
37+
}
38+
39+
func (f *Pidfile) Lock() error {
40+
return pidfile.Write(f.path, os.Getpid())
41+
}

pkg/e2e/watch_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ func doTest(t *testing.T, svcName string, tarSync bool) {
7171
CopyFile(t, filepath.Join("fixtures", "watch", "compose.yaml"), composeFilePath)
7272

7373
projName := "e2e-watch-" + svcName
74+
if tarSync {
75+
projName += "-tar"
76+
}
7477
env := []string{
7578
"COMPOSE_FILE=" + composeFilePath,
7679
"COMPOSE_PROJECT_NAME=" + projName,
@@ -96,6 +99,7 @@ func doTest(t *testing.T, svcName string, tarSync bool) {
9699
t.Cleanup(func() {
97100
// IMPORTANT: watch doesn't exit on its own, don't leak processes!
98101
if r.Cmd.Process != nil {
102+
t.Logf("Killing watch process: pid[%d]", r.Cmd.Process.Pid)
99103
_ = r.Cmd.Process.Kill()
100104
}
101105
})

pkg/remote/git.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"regexp"
2626
"strconv"
2727

28+
"github.com/adrg/xdg"
29+
2830
"github.com/compose-spec/compose-go/cli"
2931
"github.com/compose-spec/compose-go/loader"
3032
"github.com/compose-spec/compose-go/types"
@@ -45,19 +47,15 @@ func GitRemoteLoaderEnabled() (bool, error) {
4547
}
4648

4749
func NewGitRemoteLoader() (loader.ResourceLoader, error) {
48-
var base string
49-
if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
50-
base = cacheHome
51-
} else {
52-
home, err := os.UserHomeDir()
53-
if err != nil {
54-
return nil, err
55-
}
56-
base = filepath.Join(home, ".cache")
50+
// xdg.CacheFile creates the parent directories for the target file path
51+
// and returns the fully qualified path, so use "git" as a filename and
52+
// then chop it off after, i.e. no ~/.cache/docker-compose/git file will
53+
// ever be created
54+
cache, err := xdg.CacheFile(filepath.Join("docker-compose", "git"))
55+
if err != nil {
56+
return nil, fmt.Errorf("initializing git cache: %w", err)
5757
}
58-
cache := filepath.Join(base, "docker-compose")
59-
60-
err := os.MkdirAll(cache, 0o700)
58+
cache = filepath.Dir(cache)
6159
return gitRemoteLoader{
6260
cache: cache,
6361
}, err

0 commit comments

Comments
 (0)