github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/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  }