github.com/minio/console@v1.4.1/cmd/console/update.go (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"net"
    25  	"net/http"
    26  	"os"
    27  	"runtime"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/blang/semver/v4"
    32  	"github.com/cheggaaa/pb/v3"
    33  	"github.com/minio/cli"
    34  	"github.com/minio/console/pkg"
    35  	"github.com/minio/selfupdate"
    36  )
    37  
    38  func getUpdateTransport(timeout time.Duration) http.RoundTripper {
    39  	var updateTransport http.RoundTripper = &http.Transport{
    40  		Proxy: http.ProxyFromEnvironment,
    41  		DialContext: (&net.Dialer{
    42  			Timeout:   timeout,
    43  			KeepAlive: timeout,
    44  			DualStack: true,
    45  		}).DialContext,
    46  		IdleConnTimeout:       timeout,
    47  		TLSHandshakeTimeout:   timeout,
    48  		ExpectContinueTimeout: timeout,
    49  		DisableCompression:    true,
    50  	}
    51  	return updateTransport
    52  }
    53  
    54  func getUpdateReaderFromURL(u string, transport http.RoundTripper) (io.ReadCloser, int64, error) {
    55  	clnt := &http.Client{
    56  		Transport: transport,
    57  	}
    58  	req, err := http.NewRequest(http.MethodGet, u, nil)
    59  	if err != nil {
    60  		return nil, -1, err
    61  	}
    62  
    63  	resp, err := clnt.Do(req)
    64  	if err != nil {
    65  		return nil, -1, err
    66  	}
    67  	return resp.Body, resp.ContentLength, nil
    68  }
    69  
    70  const defaultPubKey = "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
    71  
    72  func getLatestRelease(tr http.RoundTripper) (string, error) {
    73  	releaseURL := "https://api.github.com/repos/minio/console/releases/latest"
    74  
    75  	body, _, err := getUpdateReaderFromURL(releaseURL, tr)
    76  	if err != nil {
    77  		return "", fmt.Errorf("unable to access github release URL %w", err)
    78  	}
    79  	defer body.Close()
    80  
    81  	lm := make(map[string]interface{})
    82  	if err = json.NewDecoder(body).Decode(&lm); err != nil {
    83  		return "", err
    84  	}
    85  	rel, ok := lm["tag_name"].(string)
    86  	if !ok {
    87  		return "", errors.New("unable to find latest release tag")
    88  	}
    89  	return rel, nil
    90  }
    91  
    92  // update console in-place
    93  var updateCmd = cli.Command{
    94  	Name:   "update",
    95  	Usage:  "update console to latest release",
    96  	Action: updateInplace,
    97  }
    98  
    99  func updateInplace(_ *cli.Context) error {
   100  	transport := getUpdateTransport(30 * time.Second)
   101  	rel, err := getLatestRelease(transport)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	latest, err := semver.Make(strings.TrimPrefix(rel, "v"))
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	current, err := semver.Make(pkg.Version)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	if current.GTE(latest) {
   117  		fmt.Printf("You are already running the latest version v%v.\n", pkg.Version)
   118  		return nil
   119  	}
   120  
   121  	consoleBin := fmt.Sprintf("https://github.com/minio/console/releases/download/%s/console-%s-%s", rel, runtime.GOOS, runtime.GOARCH)
   122  	reader, length, err := getUpdateReaderFromURL(consoleBin, transport)
   123  	if err != nil {
   124  		return fmt.Errorf("unable to fetch binary from %s: %w", consoleBin, err)
   125  	}
   126  
   127  	minisignPubkey := os.Getenv("CONSOLE_MINISIGN_PUBKEY")
   128  	if minisignPubkey == "" {
   129  		minisignPubkey = defaultPubKey
   130  	}
   131  
   132  	v := selfupdate.NewVerifier()
   133  	if err = v.LoadFromURL(consoleBin+".minisig", minisignPubkey, transport); err != nil {
   134  		return fmt.Errorf("unable to fetch binary signature for %s: %w", consoleBin, err)
   135  	}
   136  	opts := selfupdate.Options{
   137  		Verifier: v,
   138  	}
   139  
   140  	tmpl := `{{ red "Downloading:" }} {{bar . (red "[") (green "=") (red "]")}} {{speed . | rndcolor }}`
   141  	bar := pb.ProgressBarTemplate(tmpl).Start64(length)
   142  	barReader := bar.NewProxyReader(reader)
   143  	if err = selfupdate.Apply(barReader, opts); err != nil {
   144  		bar.Finish()
   145  		if rerr := selfupdate.RollbackError(err); rerr != nil {
   146  			return rerr
   147  		}
   148  		return err
   149  	}
   150  
   151  	bar.Finish()
   152  	fmt.Printf("Updated 'console' to latest release %s\n", rel)
   153  	return nil
   154  }