/
pause.go
177 lines (152 loc) · 5.64 KB
/
pause.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package project
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/spf13/cobra"
"github.com/mutagen-io/mutagen/cmd"
"github.com/mutagen-io/mutagen/cmd/mutagen/daemon"
"github.com/mutagen-io/mutagen/cmd/mutagen/forward"
"github.com/mutagen-io/mutagen/cmd/mutagen/sync"
"github.com/mutagen-io/mutagen/pkg/filesystem/locking"
"github.com/mutagen-io/mutagen/pkg/identifier"
"github.com/mutagen-io/mutagen/pkg/project"
"github.com/mutagen-io/mutagen/pkg/selection"
)
// pauseMain is the entry point for the pause command.
func pauseMain(_ *cobra.Command, _ []string) error {
// Compute the name of the configuration file and ensure that our working
// directory is that in which the file resides. This is required for
// relative paths (including relative synchronization paths and relative
// Unix Domain Socket paths) to be resolved relative to the project
// configuration file.
configurationFileName := project.DefaultConfigurationFileName
if pauseConfiguration.projectFile != "" {
var directory string
directory, configurationFileName = filepath.Split(pauseConfiguration.projectFile)
if directory != "" {
if err := os.Chdir(directory); err != nil {
return fmt.Errorf("unable to switch to target directory: %w", err)
}
}
}
// Compute the lock path.
lockPath := configurationFileName + project.LockFileExtension
// Track whether or not we should remove the lock file on return.
var removeLockFileOnReturn bool
// Create a locker and defer its closure and potential removal. On Windows
// systems, we have to handle this removal after the file is closed.
locker, err := locking.NewLocker(lockPath, 0600)
if err != nil {
return fmt.Errorf("unable to create project locker: %w", err)
}
defer func() {
locker.Close()
if removeLockFileOnReturn && runtime.GOOS == "windows" {
os.Remove(lockPath)
}
}()
// Acquire the project lock and defer its release and potential removal. On
// Windows systems, we can't remove the lock file if it's locked or even
// just opened, so we handle removal for Windows systems after we close the
// lock file (see above). In this case, we truncate the lock file before
// releasing it to ensure that any other process that opens or acquires the
// lock file before we manage to remove it will simply see an empty lock
// file, which it will ignore or attempt to remove.
if err := locker.Lock(true); err != nil {
return fmt.Errorf("unable to acquire project lock: %w", err)
}
defer func() {
if removeLockFileOnReturn {
if runtime.GOOS == "windows" {
locker.Truncate(0)
} else {
os.Remove(lockPath)
}
}
locker.Unlock()
}()
// Read the project identifier from the lock file. If the lock file is
// empty, then we can assume that we created it when we created the lock and
// just remove it.
buffer := &bytes.Buffer{}
if length, err := buffer.ReadFrom(locker); err != nil {
return fmt.Errorf("unable to read project lock: %w", err)
} else if length == 0 {
removeLockFileOnReturn = true
return errors.New("project not running")
}
projectIdentifier := buffer.String()
// Ensure that the project identifier is valid.
if !identifier.IsValid(projectIdentifier) {
return errors.New("invalid project identifier found in project lock")
}
// Load the configuration file.
configuration, err := project.LoadConfiguration(configurationFileName)
if err != nil {
return fmt.Errorf("unable to load configuration file: %w", err)
}
// Perform pre-pause commands.
for _, command := range configuration.BeforePause {
fmt.Println(">", command)
if err := runInShell(command); err != nil {
return fmt.Errorf("pre-pause command failed: %w", err)
}
}
// Connect to the daemon and defer closure of the connection.
daemonConnection, err := daemon.Connect(true, true)
if err != nil {
return fmt.Errorf("unable to connect to daemon: %w", err)
}
defer daemonConnection.Close()
// Compute the selection that we're going to use to pause sessions.
selection := &selection.Selection{
LabelSelector: fmt.Sprintf("%s=%s", project.LabelKey, projectIdentifier),
}
// Pause forwarding sessions.
if err := forward.PauseWithSelection(daemonConnection, selection); err != nil {
return fmt.Errorf("unable to pause forwarding session(s): %w", err)
}
// Pause synchronization sessions.
if err := sync.PauseWithSelection(daemonConnection, selection); err != nil {
return fmt.Errorf("unable to pause synchronization session(s): %w", err)
}
// Perform post-pause commands.
for _, command := range configuration.AfterPause {
fmt.Println(">", command)
if err := runInShell(command); err != nil {
return fmt.Errorf("post-pause command failed: %w", err)
}
}
// Success.
return nil
}
// pauseCommand is the pause command.
var pauseCommand = &cobra.Command{
Use: "pause",
Short: "Pause project sessions",
Args: cmd.DisallowArguments,
RunE: pauseMain,
SilenceUsage: true,
}
// pauseConfiguration stores configuration for the pause command.
var pauseConfiguration struct {
// help indicates whether or not to show help information and exit.
help bool
// projectFile is the path to the project file, if non-default.
projectFile string
}
func init() {
// Grab a handle for the command line flags.
flags := pauseCommand.Flags()
// Disable alphabetical sorting of flags in help output.
flags.SortFlags = false
// Manually add a help flag to override the default message. Cobra will
// still implement its logic automatically.
flags.BoolVarP(&pauseConfiguration.help, "help", "h", false, "Show help information")
// Wire up project file flags.
flags.StringVarP(&pauseConfiguration.projectFile, "project-file", "f", "", "Specify project file")
}