Skip to content

Commit

Permalink
Fix get latest version operation timeout (#102)
Browse files Browse the repository at this point in the history
## What is the purpose of the change

For some reason, resty didn't query GitHub through proxy while `curl` did. So use `curl` instead.

## Brief change log

- Use the command `curl` to get the latest version
- Add debug log to print background task error.
- Set state.yml when the update CLI operation is finished.
  • Loading branch information
xuanyu66 authored May 23, 2023
1 parent 0a83094 commit b00dec2
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,4 @@ jobs:
- name: golangci-lint
uses: golangci/[email protected]
with:
version: v1.50.1
version: v1.52.2
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
GOLANGCI_VERSION=v1.50.1
GOLANGCI_VERSION=v1.52.2
COVERAGE=coverage.out

.PHONY: deps
Expand Down
21 changes: 17 additions & 4 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ import (

"github.com/fatih/color"
"github.com/juju/errors"
logger "github.com/pingcap/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)

func Execute(ctx context.Context) {
Expand Down Expand Up @@ -113,7 +115,10 @@ func RootCmd(h *internal.Helper) *cobra.Command {

if shouldCheckNewRelease(cmd) {
go func() {
rel, _ := checkForUpdate(h.IOStreams.CanPrompt)
rel, err := checkForUpdate(cmd.Context(), h.IOStreams.CanPrompt)
if err != nil {
logger.Debug("Error checking for new release", zap.Error(err))
}
updateMessageChan <- rel
}()
}
Expand Down Expand Up @@ -196,12 +201,13 @@ func shouldCheckAuth(cmd *cobra.Command) bool {
return true
}

func checkForUpdate(isTerminal bool) (*github.ReleaseInfo, error) {
func checkForUpdate(ctx context.Context, isTerminal bool) (*github.ReleaseInfo, error) {
if !isTerminal || ver.Version == ver.DevVersion {
logger.Debug("Skip checking for new release", zap.Bool("isTerminal", isTerminal), zap.String("version", ver.Version))
return nil, nil
}

return github.CheckForUpdate(config.Repo, true)
return github.CheckForUpdate(ctx, true)
}

// initConfig reads in config file and ENV variables if set.
Expand All @@ -221,7 +227,14 @@ func initConfig() {
viper.SetConfigType("toml")
viper.SetConfigName("config")
viper.SetConfigPermissions(0600)
_ = viper.SafeWriteConfig()
err = viper.SafeWriteConfig()
if err != nil {
var existErr viper.ConfigFileAlreadyExistsError
if !errors.As(err, &existErr) {
color.Red("Failed to write config file: %s", err.Error())
os.Exit(1)
}
}

// After version 0.1.2, we replace underscore with hyphen in properties.
// In order to keep backward compatibility, we need to replace the old names to the new ones.
Expand Down
5 changes: 4 additions & 1 deletion internal/cli/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/fatih/color"
"github.com/juju/errors"
"github.com/pingcap/log"
"github.com/spf13/cobra"
"go.uber.org/zap"
)

func UpdateCmd(h *internal.Helper) *cobra.Command {
Expand All @@ -45,9 +47,10 @@ func UpdateCmd(h *internal.Helper) *cobra.Command {
}

// When update CLI, we don't need to check the version again after command executes.
newRelease, err := github.CheckForUpdate(config.Repo, false)
newRelease, err := github.CheckForUpdate(ctx, false)
// If we can't get the latest version, we should update the CLI assuming it's not the latest version.
if err != nil {
log.Debug("Failed to check for update, update the CLI assuming it's not the latest version", zap.Error(err))
newRelease = &github.ReleaseInfo{
Version: "latest",
}
Expand Down
2 changes: 0 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ const (
cliNameInTiUP = "cloud"
HomePath = ".ticloud"

Repo = "tidbcloud/tidbcloud-cli"

Confirmed = "yes"
)

Expand Down
5 changes: 4 additions & 1 deletion internal/service/cloud/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ func RetrieveImports(pID string, cID string, pageSize int64, d TiDBCloudClient)
return 0, nil, errors.Trace(err)
}

total, _ = strconv.ParseUint(*imports.Payload.Total, 0, 64)
total, err = strconv.ParseUint(*imports.Payload.Total, 0, 64)
if err != nil {
return 0, nil, errors.Annotate(err, " failed parse total import number.")
}
page += 1
items = append(items, imports.Payload.Imports...)
}
Expand Down
55 changes: 30 additions & 25 deletions internal/service/github/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@
package github

import (
"fmt"
"bytes"
"context"
"os"
"path/filepath"
"time"

"tidbcloud-cli/internal/config"
ver "tidbcloud-cli/internal/version"

"github.com/go-resty/resty/v2"
"github.com/hashicorp/go-version"
"github.com/juju/errors"
exec "golang.org/x/sys/execabs"
"gopkg.in/yaml.v3"
)

Expand All @@ -42,8 +43,12 @@ type StateEntry struct {
// CheckForUpdate checks for updates and returns the latest release info if there is a newer version.
// For checking after every command, we should use stateEntry to avoid checking too frequently.
// For checking when using `update`, we should use forceCheck to ignore the stateEntry.
func CheckForUpdate(repo string, withRateLimit bool) (*ReleaseInfo, error) {
home, _ := os.UserHomeDir()
func CheckForUpdate(ctx context.Context, withRateLimit bool) (*ReleaseInfo, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}

stateFilePath := filepath.Join(home, config.HomePath, "state.yml")
if withRateLimit {
stateEntry, _ := getStateEntry(stateFilePath)
Expand All @@ -52,16 +57,14 @@ func CheckForUpdate(repo string, withRateLimit bool) (*ReleaseInfo, error) {
}
}

releaseInfo, err := getLatestReleaseInfo(repo)
releaseInfo, err := getLatestReleaseInfo(ctx)
if err != nil {
return nil, err
}

if withRateLimit {
err = setStateEntry(stateFilePath, time.Now(), *releaseInfo)
if err != nil {
return nil, err
}
err = setStateEntry(stateFilePath, time.Now(), *releaseInfo)
if err != nil {
return nil, err
}

if versionGreaterThan(releaseInfo.Version, ver.Version) {
Expand All @@ -86,27 +89,29 @@ func getStateEntry(stateFilePath string) (*StateEntry, error) {
return &stateEntry, nil
}

func getLatestReleaseInfo(repo string) (*ReleaseInfo, error) {
client := resty.New()
client.SetTimeout(5 * time.Second)
response, err := client.
R().
Get(fmt.Sprintf("https://raw.githubusercontent.com/%s/main/latest-version", repo))
func getLatestReleaseInfo(ctx context.Context) (*ReleaseInfo, error) {
ctx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()

c1 := exec.CommandContext(ctx, "curl", "-sSL", "https://raw.githubusercontent.com/tidbcloud/tidbcloud-cli/main/latest-version") //nolint:gosec
var stdout bytes.Buffer
var stderr bytes.Buffer
c1.Stdout = &stdout
c1.Stderr = &stderr

err := c1.Run()
if ctx.Err() == context.DeadlineExceeded {
return nil, errors.New("timeout when get latest CLI version")
}
if err != nil {
return nil, errors.Trace(err)
return nil, errors.Annotate(err, stderr.String())
}

body := string(response.Body())
_, err = version.NewVersion(body)
_, err = version.NewVersion(stdout.String())
if err != nil {
return nil, err
}
latestRelease := ReleaseInfo{Version: body}
if response.IsSuccess() {
return &latestRelease, nil
} else {
return nil, errors.Errorf("failed to get latest release info: %s", response.Status()+" "+body)
}
return &ReleaseInfo{Version: stdout.String()}, nil
}

func setStateEntry(stateFilePath string, t time.Time, r ReleaseInfo) error {
Expand Down
12 changes: 10 additions & 2 deletions internal/ui/spinner_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (

"github.com/charmbracelet/lipgloss"
"github.com/fatih/color"
"github.com/pingcap/log"
"go.uber.org/zap"

"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
Expand Down Expand Up @@ -54,8 +56,14 @@ func (m SpinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
p, _ := os.FindProcess(os.Getpid())
_ = p.Signal(syscall.SIGINT)
p, err := os.FindProcess(os.Getpid())
if err != nil {
log.Debug("failed to find current process when interrupted in spinner", zap.Error(err))
}
err = p.Signal(syscall.SIGINT)
if err != nil {
log.Debug("failed to send SIGINT to current process when interrupted in spinner", zap.Error(err))
}
m.Interrupted = true
return m, nil
default:
Expand Down

0 comments on commit b00dec2

Please sign in to comment.