github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/update.go (about) 1 /* 2 * Copyright 2018-2020 The NATS Authors 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package cmd 17 18 import ( 19 "fmt" 20 "os" 21 "strings" 22 "time" 23 24 "github.com/blang/semver" 25 26 "github.com/briandowns/spinner" 27 cli "github.com/nats-io/cliprompts/v2" 28 "github.com/rhysd/go-github-selfupdate/selfupdate" 29 "github.com/spf13/cobra" 30 ) 31 32 type semV string 33 34 func (r *semV) String() string { 35 if *r == "" { 36 return "latest" 37 } 38 return string(*r) 39 } 40 41 func (r *semV) Type() string { 42 return "version" 43 } 44 45 func (r *semV) Set(s string) error { 46 if strings.ToLower(strings.TrimSpace(s)) == "latest" { 47 *r = "" 48 } else if v, err := semver.ParseTolerant(s); err != nil { 49 return err 50 } else { 51 *r = semV(v.String()) 52 } 53 return nil 54 } 55 56 func createUpdateCommand() *cobra.Command { 57 ver := semV("") 58 var cmd = &cobra.Command{ 59 Example: "nsc update", 60 Use: "update", 61 Short: "Update this tool to latest version", 62 Args: MaxArgs(0), 63 RunE: func(cmd *cobra.Command, args []string) error { 64 v, err := semver.ParseTolerant(GetRootCmd().Version) 65 if err != nil { 66 return err 67 } 68 cmdPath, err := os.Executable() 69 if err != nil { 70 return err 71 } 72 su, err := NewSelfUpdate() 73 if err != nil { 74 return err 75 } 76 nvs, err := su.doCheck(string(ver)) 77 if err != nil { 78 return err 79 } 80 if nvs == nil { 81 cmd.Println("Current version", v, "is requested version") 82 return nil 83 } 84 85 wait := spinner.New(spinner.CharSets[14], 250*time.Millisecond) 86 defer wait.Stop() 87 88 wait.Prefix = fmt.Sprintf("Downloading version: %s", ver.String()) 89 _ = wait.Color("italic") 90 wait.Start() 91 92 if updateFn == nil { 93 // the library freak out if GITHUB_TOKEN is set - don't break travis :) 94 _ = os.Setenv("GITHUB_TOKEN", "") 95 96 err = selfupdate.DefaultUpdater().UpdateTo(nvs, cmdPath) 97 } else { 98 err = updateFn(nvs, cmdPath) 99 } 100 if err != nil { 101 cmd.SilenceErrors = false 102 return err 103 } 104 105 cmd.Printf("Successfully updated to version %s\n", nvs.Version.String()) 106 cmd.Println() 107 cmd.Println("Release Notes:") 108 cmd.Println() 109 cmd.Println(cli.Wrap(80, nvs.ReleaseNotes)) 110 111 return nil 112 }, 113 } 114 cmd.Flags().Var(&ver, "version", "version to updated the nsc binary to") 115 return cmd 116 } 117 118 func init() { 119 GetRootCmd().AddCommand(createUpdateCommand()) 120 } 121 122 type UpdateCheckFn func(slug string, wantVer string) (*selfupdate.Release, bool, error) 123 type UpdateFn func(want *selfupdate.Release, cmdPath string) error 124 125 var updateCheckFn UpdateCheckFn 126 var updateFn UpdateFn 127 128 type SelfUpdate struct { 129 } 130 131 // NewSelfUpdate creates a new self update object 132 func NewSelfUpdate() (*SelfUpdate, error) { 133 if GetConfig().GithubUpdates == "" { 134 return nil, fmt.Errorf("unable to check for updates - repository not set") 135 } 136 u := &SelfUpdate{} 137 return u, nil 138 } 139 140 func (u *SelfUpdate) Run() (*semver.Version, error) { 141 if !u.shouldCheck() { 142 return nil, nil 143 } 144 rel, err := u.doCheck("") 145 if err != nil { 146 // stop checking for a bit 147 _ = u.updateLastChecked() 148 return nil, err 149 } 150 err = u.updateLastChecked() 151 if rel == nil { 152 return nil, err 153 } else { 154 return &rel.Version, err 155 } 156 } 157 158 func (u *SelfUpdate) shouldCheck() bool { 159 have := semver.MustParse(GetRootCmd().Version).String() 160 if have == "0.0.0-dev" { 161 return false 162 } 163 config := GetConfig() 164 now := time.Now().Unix() 165 diff := now - config.LastUpdate 166 167 return config.LastUpdate == 0 || diff > int64(60*60*24) 168 } 169 170 func (u *SelfUpdate) updateLastChecked() error { 171 config := GetConfig() 172 config.LastUpdate = time.Now().Unix() 173 return config.Save() 174 } 175 176 func (u *SelfUpdate) doCheck(wantVer string) (*selfupdate.Release, error) { 177 config := GetConfig() 178 have, err := semver.ParseTolerant(GetRootCmd().Version) 179 if err != nil { 180 return nil, err 181 } 182 if wantVer != "" { 183 want, err := semver.ParseTolerant(wantVer) 184 if err != nil { 185 return nil, err 186 } 187 wantVer = want.String() 188 } 189 wait := spinner.New(spinner.CharSets[14], 100*time.Millisecond) 190 if wantVer == "" { 191 wait.Prefix = "Checking for latest version " 192 } else { 193 wait.Prefix = fmt.Sprintf("Checking for version %s ", wantVer) 194 } 195 _ = wait.Color("italic") 196 wait.Start() 197 defer wait.Stop() 198 199 var want *selfupdate.Release 200 var found bool 201 if updateCheckFn == nil { 202 // the library freak out if GITHUB_TOKEN is set - don't break travis :) 203 _ = os.Setenv("GITHUB_TOKEN", "") 204 if wantVer == "" { 205 want, found, err = selfupdate.DetectLatest(config.GithubUpdates) 206 } else { 207 want, found, err = selfupdate.DetectVersion(GetConfig().GithubUpdates, wantVer) 208 } 209 } else { 210 want, found, err = updateCheckFn(config.GithubUpdates, wantVer) 211 } 212 if err != nil { 213 return nil, fmt.Errorf("error checking version: %v", err) 214 } else if !found { 215 return nil, fmt.Errorf("version %v not found", wantVer) 216 } else if !want.Version.EQ(have) { 217 return want, nil 218 } 219 return nil, nil 220 }