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 }