github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/runners/update/lock.go (about)

     1  package update
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/ActiveState/cli/internal/analytics"
     7  	"github.com/ActiveState/cli/internal/captain"
     8  	"github.com/ActiveState/cli/internal/constants"
     9  	"github.com/ActiveState/cli/internal/errs"
    10  	"github.com/ActiveState/cli/internal/locale"
    11  	"github.com/ActiveState/cli/internal/multilog"
    12  	"github.com/ActiveState/cli/internal/output"
    13  	"github.com/ActiveState/cli/internal/prompt"
    14  	"github.com/ActiveState/cli/internal/runbits/rationalize"
    15  	"github.com/ActiveState/cli/internal/updater"
    16  	"github.com/ActiveState/cli/pkg/platform/model"
    17  	"github.com/ActiveState/cli/pkg/project"
    18  	"github.com/ActiveState/cli/pkg/projectfile"
    19  )
    20  
    21  // var _ captain.FlagMarshaler = (*StateToolChannelVersion)(nil)
    22  
    23  type StateToolChannelVersion struct {
    24  	captain.NameVersionValue
    25  }
    26  
    27  func (stv *StateToolChannelVersion) Set(arg string) error {
    28  	err := stv.NameVersionValue.Set(arg)
    29  	if err != nil {
    30  		return locale.WrapInputError(
    31  			err,
    32  			"err_channel_format",
    33  			"The State Tool channel and version provided is not formatting correctly, must be in the form of <channel>@<version>",
    34  		)
    35  	}
    36  	return nil
    37  }
    38  
    39  func (stv *StateToolChannelVersion) Type() string {
    40  	return "channel"
    41  }
    42  
    43  type LockParams struct {
    44  	Channel        StateToolChannelVersion
    45  	NonInteractive bool
    46  }
    47  
    48  type Lock struct {
    49  	project *project.Project
    50  	out     output.Outputer
    51  	prompt  prompt.Prompter
    52  	cfg     updater.Configurable
    53  	an      analytics.Dispatcher
    54  	svc     *model.SvcModel
    55  }
    56  
    57  func NewLock(prime primeable) *Lock {
    58  	return &Lock{
    59  		prime.Project(),
    60  		prime.Output(),
    61  		prime.Prompt(),
    62  		prime.Config(),
    63  		prime.Analytics(),
    64  		prime.SvcModel(),
    65  	}
    66  }
    67  
    68  func (l *Lock) Run(params *LockParams) error {
    69  	if l.project == nil {
    70  		return rationalize.ErrNoProject
    71  	}
    72  
    73  	l.out.Notice(locale.Tl("locking_version", "Locking State Tool version for current project."))
    74  
    75  	if l.project.IsLocked() && !params.NonInteractive {
    76  		if err := confirmLock(l.prompt); err != nil {
    77  			return locale.WrapError(err, "err_update_lock_confirm", "Could not confirm whether to lock update.")
    78  		}
    79  	}
    80  
    81  	// invalidate the installer version lock if `state update lock` is requested
    82  	if err := l.cfg.Set(updater.CfgKeyInstallVersion, ""); err != nil {
    83  		multilog.Error("Failed to invalidate installer version lock on `state update lock` invocation: %v", err)
    84  	}
    85  
    86  	defaultChannel, lockVersion := params.Channel.Name(), params.Channel.Version()
    87  	prefer := true
    88  	if defaultChannel == "" {
    89  		defaultChannel = l.project.Channel()
    90  		prefer = false // may be overwritten by env var
    91  	}
    92  	channel := fetchChannel(defaultChannel, prefer)
    93  
    94  	var version string
    95  	if l.project.IsLocked() && channel == l.project.Channel() {
    96  		version = l.project.Version()
    97  	}
    98  
    99  	exactVersion, err := fetchExactVersion(l.svc, channel, version)
   100  	if err != nil {
   101  		return errs.Wrap(err, "fetchUpdater failed, version: %s, channel: %s", version, channel)
   102  	}
   103  
   104  	if lockVersion == "" {
   105  		lockVersion = exactVersion
   106  	}
   107  
   108  	err = l.cfg.Set(constants.AutoUpdateConfigKey, "false")
   109  	if err != nil {
   110  		return locale.WrapError(err, "err_lock_disable_autoupdate", "Unable to disable automatic updates prior to locking")
   111  	}
   112  
   113  	err = projectfile.AddLockInfo(l.project.Source().Path(), channel, lockVersion)
   114  	if err != nil {
   115  		return locale.WrapError(err, "err_update_projectfile", "Could not update projectfile")
   116  	}
   117  
   118  	l.out.Print(output.Prepare(
   119  		locale.Tl("version_locked", "Version locked at {{.V0}}@{{.V1}}", channel, lockVersion),
   120  		&struct {
   121  			Channel string `json:"channel"`
   122  			Version string `json:"version"`
   123  		}{
   124  			channel,
   125  			lockVersion,
   126  		},
   127  	))
   128  	return nil
   129  }
   130  
   131  func confirmLock(prom prompt.Prompter) error {
   132  	msg := locale.T("confirm_update_locked_version_prompt")
   133  
   134  	confirmed, err := prom.Confirm(locale.T("confirm"), msg, new(bool))
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	if !confirmed {
   140  		return locale.NewInputError("err_update_lock_noconfirm", "Cancelling by your request.")
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  func fetchExactVersion(svc *model.SvcModel, channel, version string) (string, error) {
   147  	upd, err := svc.CheckUpdate(context.Background(), channel, version)
   148  	if err != nil {
   149  		return "", locale.WrapExternalError(err, "err_update_fetch", "Could not retrieve update information, please verify that '{{.V0}}' is a valid channel.", channel)
   150  	}
   151  
   152  	return upd.Version, nil
   153  }