123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- package cwhub
- // Install, upgrade and remove items from the hub to the local configuration
- // XXX: this file could use a better name
- import (
- "bytes"
- "crypto/sha256"
- "fmt"
- "io"
- "net/http"
- "os"
- "path/filepath"
- "strings"
- "github.com/enescakir/emoji"
- log "github.com/sirupsen/logrus"
- )
- // InstallItem installs an item from the hub
- func (h *Hub) InstallItem(name string, itemType string, force bool, downloadOnly bool) error {
- item := h.GetItem(itemType, name)
- if item == nil {
- return fmt.Errorf("unable to retrieve item: %s", name)
- }
- if downloadOnly && item.Downloaded && item.UpToDate {
- log.Warningf("%s is already downloaded and up-to-date", item.Name)
- if !force {
- return nil
- }
- }
- err := h.DownloadLatest(item, force, true)
- if err != nil {
- return fmt.Errorf("while downloading %s: %w", item.Name, err)
- }
- if err = h.AddItem(*item); err != nil {
- return fmt.Errorf("while adding %s: %w", item.Name, err)
- }
- if downloadOnly {
- log.Infof("Downloaded %s to %s", item.Name, filepath.Join(h.cfg.HubDir, item.RemotePath))
- return nil
- }
- err = h.EnableItem(item)
- if err != nil {
- return fmt.Errorf("while enabling %s: %w", item.Name, err)
- }
- if err := h.AddItem(*item); err != nil {
- return fmt.Errorf("while adding %s: %w", item.Name, err)
- }
- log.Infof("Enabled %s", item.Name)
- return nil
- }
- // RemoveItem removes one - or all - the items from the hub
- func (h *Hub) RemoveMany(itemType string, name string, all bool, purge bool, forceAction bool) error {
- if name != "" {
- item := h.GetItem(itemType, name)
- if item == nil {
- return fmt.Errorf("can't find '%s' in %s", name, itemType)
- }
- err := h.DisableItem(item, purge, forceAction)
- if err != nil {
- return fmt.Errorf("unable to disable %s: %w", item.Name, err)
- }
- if err = h.AddItem(*item); err != nil {
- return fmt.Errorf("unable to add %s: %w", item.Name, err)
- }
- return nil
- }
- if !all {
- return fmt.Errorf("removing item: no item specified")
- }
- disabled := 0
- // remove all
- for _, v := range h.GetItemMap(itemType) {
- if !v.Installed {
- continue
- }
- err := h.DisableItem(&v, purge, forceAction)
- if err != nil {
- return fmt.Errorf("unable to disable %s: %w", v.Name, err)
- }
- if err := h.AddItem(v); err != nil {
- return fmt.Errorf("unable to add %s: %w", v.Name, err)
- }
- disabled++
- }
- log.Infof("Disabled %d items", disabled)
- return nil
- }
- // UpgradeConfig upgrades an item from the hub
- func (h *Hub) UpgradeConfig(itemType string, name string, force bool) error {
- updated := 0
- found := false
- for _, v := range h.GetItemMap(itemType) {
- if name != "" && name != v.Name {
- continue
- }
- if !v.Installed {
- log.Tracef("skip %s, not installed", v.Name)
- continue
- }
- if !v.Downloaded {
- log.Warningf("%s: not downloaded, please install.", v.Name)
- continue
- }
- found = true
- if v.UpToDate {
- log.Infof("%s: up-to-date", v.Name)
- if err := h.DownloadDataIfNeeded(v, force); err != nil {
- return fmt.Errorf("%s: download failed: %w", v.Name, err)
- }
- if !force {
- continue
- }
- }
- if err := h.DownloadLatest(&v, force, true); err != nil {
- return fmt.Errorf("%s: download failed: %w", v.Name, err)
- }
- if !v.UpToDate {
- if v.Tainted {
- log.Infof("%v %s is tainted, --force to overwrite", emoji.Warning, v.Name)
- } else if v.Local {
- log.Infof("%v %s is local", emoji.Prohibited, v.Name)
- }
- } else {
- // this is used while scripting to know if the hub has been upgraded
- // and a configuration reload is required
- fmt.Printf("updated %s\n", v.Name)
- log.Infof("%v %s : updated", emoji.Package, v.Name)
- updated++
- }
- if err := h.AddItem(v); err != nil {
- return fmt.Errorf("unable to add %s: %w", v.Name, err)
- }
- }
- if !found && name == "" {
- log.Infof("No %s installed, nothing to upgrade", itemType)
- } else if !found {
- log.Errorf("can't find '%s' in %s", name, itemType)
- } else if updated == 0 && found {
- if name == "" {
- log.Infof("All %s are already up-to-date", itemType)
- } else {
- log.Infof("Item '%s' is up-to-date", name)
- }
- } else if updated != 0 {
- log.Infof("Upgraded %d items", updated)
- }
- return nil
- }
- // DownloadLatest will download the latest version of Item to the tdir directory
- func (h *Hub) DownloadLatest(target *Item, overwrite bool, updateOnly bool) error {
- var err error
- log.Debugf("Downloading %s %s", target.Type, target.Name)
- if target.Type != COLLECTIONS {
- if !target.Installed && updateOnly && target.Downloaded {
- log.Debugf("skipping upgrade of %s : not installed", target.Name)
- return nil
- }
- return h.DownloadItem(target, overwrite)
- }
- // collection
- for _, sub := range target.SubItems() {
- val, ok := h.Items[sub.Type][sub.Name]
- if !ok {
- return fmt.Errorf("required %s %s of %s doesn't exist, abort", sub.Type, sub.Name, target.Name)
- }
- if !val.Installed && updateOnly && val.Downloaded {
- log.Debugf("skipping upgrade of %s : not installed", target.Name)
- continue
- }
- log.Debugf("Download %s sub-item : %s %s (%t -> %t)", target.Name, sub.Type, sub.Name, target.Installed, updateOnly)
- //recurse as it's a collection
- if sub.Type == COLLECTIONS {
- log.Tracef("collection, recurse")
- err = h.DownloadLatest(&val, overwrite, updateOnly)
- if err != nil {
- return fmt.Errorf("while downloading %s: %w", val.Name, err)
- }
- }
- downloaded := val.Downloaded
- err = h.DownloadItem(&val, overwrite)
- if err != nil {
- return fmt.Errorf("while downloading %s: %w", val.Name, err)
- }
- // We need to enable an item when it has been added to a collection since latest release of the collection.
- // We check if val.Downloaded is false because maybe the item has been disabled by the user.
- if !val.Installed && !downloaded {
- if err = h.EnableItem(&val); err != nil {
- return fmt.Errorf("enabling '%s': %w", val.Name, err)
- }
- }
- h.Items[sub.Type][sub.Name] = val
- }
- err = h.DownloadItem(target, overwrite)
- if err != nil {
- return fmt.Errorf("failed to download item: %w", err)
- }
- return nil
- }
- func (h *Hub) DownloadItem(target *Item, overwrite bool) error {
- tdir := h.cfg.HubDir
- // if user didn't --force, don't overwrite local, tainted, up-to-date files
- if !overwrite {
- if target.Tainted {
- log.Debugf("%s : tainted, not updated", target.Name)
- return nil
- }
- if target.UpToDate {
- // We still have to check if data files are present
- log.Debugf("%s : up-to-date, not updated", target.Name)
- }
- }
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf(RawFileURLTemplate, HubBranch, target.RemotePath), nil)
- if err != nil {
- return fmt.Errorf("while downloading %s: %w", req.URL.String(), err)
- }
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- return fmt.Errorf("while downloading %s: %w", req.URL.String(), err)
- }
- if resp.StatusCode != http.StatusOK {
- return fmt.Errorf("bad http code %d for %s", resp.StatusCode, req.URL.String())
- }
- defer resp.Body.Close()
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return fmt.Errorf("while reading %s: %w", req.URL.String(), err)
- }
- hash := sha256.New()
- if _, err = hash.Write(body); err != nil {
- return fmt.Errorf("while hashing %s: %w", target.Name, err)
- }
- meow := fmt.Sprintf("%x", hash.Sum(nil))
- if meow != target.Versions[target.Version].Digest {
- log.Errorf("Downloaded version doesn't match index, please 'hub update'")
- log.Debugf("got %s, expected %s", meow, target.Versions[target.Version].Digest)
- return fmt.Errorf("invalid download hash for %s", target.Name)
- }
- //all good, install
- //check if parent dir exists
- tmpdirs := strings.Split(tdir+"/"+target.RemotePath, "/")
- parentDir := strings.Join(tmpdirs[:len(tmpdirs)-1], "/")
- // ensure that target file is within target dir
- finalPath, err := filepath.Abs(tdir + "/" + target.RemotePath)
- if err != nil {
- return fmt.Errorf("filepath.Abs error on %s: %w", tdir+"/"+target.RemotePath, err)
- }
- if !strings.HasPrefix(finalPath, tdir) {
- return fmt.Errorf("path %s escapes %s, abort", target.RemotePath, tdir)
- }
- // check dir
- if _, err = os.Stat(parentDir); os.IsNotExist(err) {
- log.Debugf("%s doesn't exist, create", parentDir)
- if err = os.MkdirAll(parentDir, os.ModePerm); err != nil {
- return fmt.Errorf("while creating parent directories: %w", err)
- }
- }
- // check actual file
- if _, err = os.Stat(finalPath); !os.IsNotExist(err) {
- log.Warningf("%s : overwrite", target.Name)
- log.Debugf("target: %s/%s", tdir, target.RemotePath)
- } else {
- log.Infof("%s : OK", target.Name)
- }
- f, err := os.OpenFile(tdir+"/"+target.RemotePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
- if err != nil {
- return fmt.Errorf("while opening file: %w", err)
- }
- defer f.Close()
- _, err = f.Write(body)
- if err != nil {
- return fmt.Errorf("while writing file: %w", err)
- }
- target.Downloaded = true
- target.Tainted = false
- target.UpToDate = true
- if err = downloadData(h.cfg.InstallDataDir, overwrite, bytes.NewReader(body)); err != nil {
- return fmt.Errorf("while downloading data for %s: %w", target.FileName, err)
- }
- h.Items[target.Type][target.Name] = *target
- return nil
- }
- // DownloadDataIfNeeded downloads the data files for an item
- func (h *Hub) DownloadDataIfNeeded(target Item, force bool) error {
- itemFilePath := fmt.Sprintf("%s/%s/%s/%s", h.cfg.InstallDir, target.Type, target.Stage, target.FileName)
- itemFile, err := os.Open(itemFilePath)
- if err != nil {
- return fmt.Errorf("while opening %s: %w", itemFilePath, err)
- }
- defer itemFile.Close()
- if err = downloadData(h.cfg.InstallDataDir, force, itemFile); err != nil {
- return fmt.Errorf("while downloading data for %s: %w", itemFilePath, err)
- }
- return nil
- }
|