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