github.com/tomwright/dasel@v1.27.3/internal/selfupdate/release.go (about) 1 package selfupdate 2 3 import ( 4 "fmt" 5 "net/http" 6 "os" 7 "path/filepath" 8 "time" 9 ) 10 11 const ( 12 gitHubUsername = "TomWright" 13 gitHubRepo = "dasel" 14 ) 15 16 // NewUpdater returns an Updater. 17 func NewUpdater(currentVersion string) *Updater { 18 return &Updater{ 19 httpClient: &http.Client{ 20 Timeout: time.Second * 10, 21 }, 22 currentVersion: currentVersion, 23 24 FetchReleaseFn: fetchGitHubRelease, 25 DownloadFileFn: downloadFile, 26 ChmodFn: os.Chmod, 27 ExecuteCmdFn: executeCmd, 28 ExecutableFn: os.Executable, 29 RenameFn: os.Rename, 30 RemoveFn: os.Remove, 31 } 32 } 33 34 // Release is a github release. 35 type Release struct { 36 URL string `json:"url"` 37 Assets []*ReleaseAsset `json:"assets"` 38 Name string `json:"name"` 39 TagName string `json:"tag_name"` 40 } 41 42 // FindAssetForSystem searches returns the asset for the given OS and arch. 43 func (r *Release) FindAssetForSystem(os string, arch string) *ReleaseAsset { 44 ext := "" 45 if os == "windows" { 46 ext = ".exe" 47 } 48 matches := []string{fmt.Sprintf("dasel_%s_%s%s", os, arch, ext)} 49 if os == "darwin" { 50 matches = append(matches, fmt.Sprintf("dasel_%s_%s%s", "macos", arch, ext)) 51 } 52 53 for _, a := range r.Assets { 54 for _, m := range matches { 55 if a.Name == m { 56 return a 57 } 58 } 59 } 60 61 return nil 62 } 63 64 // Version returns the version of the release. 65 func (r *Release) Version() *Version { 66 return versionFromString(r.TagName) 67 } 68 69 // ReleaseAsset is an asset of a Release. 70 type ReleaseAsset struct { 71 URL string `json:"url"` 72 Name string `json:"name"` 73 BrowserDownloadURL string `json:"browser_download_url"` 74 } 75 76 // Updater provides functionality that allows you to update a binary from github. 77 type Updater struct { 78 httpClient *http.Client 79 currentVersion string 80 81 // FetchReleaseFn is used to fetch github release information. 82 FetchReleaseFn func(httpClient *http.Client, user string, repo string, tag string) (*Release, error) 83 // DownloadFileFn is used to download a file. 84 DownloadFileFn func(url string, dest string) error 85 // ChmodFn is used to change the permissions of a file. 86 ChmodFn func(name string, mode os.FileMode) error 87 // ExecuteCmdFn executes the given command and returns the output. 88 ExecuteCmdFn func(name string, arg ...string) ([]byte, error) 89 // ExecutableFn is used to return the path of the current executable. 90 ExecutableFn func() (string, error) 91 // RenameFn is used to rename a file. 92 RenameFn func(src string, dst string) error 93 // RemoveFn is used to remove a file. 94 RemoveFn func(path string) error 95 } 96 97 // FindLatestRelease returns the latest release on the given repo. 98 func (u *Updater) FindLatestRelease() (*Release, error) { 99 return u.FetchReleaseFn(u.httpClient, gitHubUsername, gitHubRepo, "latest") 100 } 101 102 // DownloadAsset downloads and returns a path to the given asset. 103 func (u *Updater) DownloadAsset(asset *ReleaseAsset) (string, error) { 104 path, err := filepath.Abs(fmt.Sprintf("./%s", asset.Name)) 105 if err != nil { 106 return "", err 107 } 108 109 if err := u.DownloadFileFn(asset.BrowserDownloadURL, path); err != nil { 110 return "", fmt.Errorf("could not download file: %w", err) 111 } 112 113 if err := u.ChmodFn(path, os.ModePerm); err != nil { 114 return "", fmt.Errorf("could not make executable: %w", err) 115 } 116 117 return path, nil 118 } 119 120 // CurrentVersion returns the current version. 121 func (u *Updater) CurrentVersion() *Version { 122 return versionFromString(u.currentVersion) 123 } 124 125 // GetVersion returns the version of the given executable. 126 func (u *Updater) GetVersion(path string) (*Version, error) { 127 versionOutput, err := u.ExecuteCmdFn(path, "--version") 128 if err != nil { 129 return nil, fmt.Errorf("could not get new version: %w", err) 130 } 131 132 latest := versionFromString(string(versionOutput)) 133 134 return latest, nil 135 } 136 137 // Replace replaces the current executable with the given executable. 138 func (u *Updater) Replace(path string) error { 139 currentPath, err := u.ExecutableFn() 140 if err != nil { 141 return fmt.Errorf("cannot get current executable path: %w", err) 142 } 143 144 if err := u.RenameFn(path, currentPath); err != nil { 145 return fmt.Errorf("could not replace old executable: %w", err) 146 } 147 148 return nil 149 } 150 151 // CleanUp cleans up the given path. 152 // This should be deferred once a download has completed. 153 func (u *Updater) CleanUp(path string) { 154 _ = u.RemoveFn(path) 155 }