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  }