forked from mirror/coolify-cli
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 780b3674c7 | |||
| 77adbfaebc | |||
| 9215fd537e |
+2
-21
@@ -6,7 +6,6 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
compareVersion "github.com/hashicorp/go-version"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@@ -144,24 +143,6 @@ func initConfig() {
|
||||
// They are loaded on-demand by getAPIClient() based on --instance or default instance
|
||||
// This allows --instance flag to work correctly
|
||||
|
||||
// Check for updates
|
||||
latestVersionStr, err := version.CheckLatestVersionOfCli(Debug)
|
||||
if err != nil {
|
||||
if Debug {
|
||||
log.Println("Failed to check for updates:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Compare versions properly using semantic versioning
|
||||
if latestVersionStr != "" {
|
||||
latestVersion, err := compareVersion.NewVersion(latestVersionStr)
|
||||
if err == nil {
|
||||
currentVersion, err := compareVersion.NewVersion(version.GetVersion())
|
||||
if err == nil && latestVersion.GreaterThan(currentVersion) {
|
||||
if Debug {
|
||||
log.Printf("New version of Coolify CLI is available: %s\n", latestVersionStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for updates (errors are handled silently inside the function)
|
||||
_, _ = version.CheckLatestVersionOfCli(Debug)
|
||||
}
|
||||
|
||||
+41
-43
@@ -5,92 +5,90 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
compareVersion "github.com/hashicorp/go-version"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Version variables injected by GoReleaser at build time via ldflags
|
||||
var (
|
||||
version = "v1.1"
|
||||
version = "v1.2"
|
||||
)
|
||||
|
||||
// GitHubAPIURL is the URL for fetching CLI version tags (exported for testing)
|
||||
var GitHubAPIURL = "https://api.github.com/repos/coollabsio/coolify-cli/git/refs/tags"
|
||||
|
||||
func GetVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
// CheckInterval for version checking
|
||||
const CheckInterval = 10 * time.Minute
|
||||
|
||||
// Tag represents a git tag for version checking
|
||||
type Tag struct {
|
||||
Ref string `json:"ref"`
|
||||
}
|
||||
|
||||
// CheckLatestVersionOfCli checks for CLI updates
|
||||
func CheckLatestVersionOfCli(debug bool) (string, error) {
|
||||
lastCheck := viper.GetString("lastupdatechecktime")
|
||||
if lastCheck != "" {
|
||||
lastCheckTime, err := time.Parse(time.RFC3339, lastCheck)
|
||||
if err == nil && lastCheckTime.Add(CheckInterval).After(time.Now()) {
|
||||
if debug {
|
||||
log.Println("Skipping update check. Last check was less than 10 minutes ago.")
|
||||
}
|
||||
return GetVersion(), nil
|
||||
}
|
||||
}
|
||||
// CheckLatestVersionOfCli checks for CLI updates on every command.
|
||||
// Errors are handled silently - the function returns without printing anything
|
||||
// if the GitHub API call fails.
|
||||
func CheckLatestVersionOfCli(_ bool) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Update check time
|
||||
viper.Set("lastupdatechecktime", time.Now().Format(time.RFC3339))
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
log.Printf("Failed to write config: %v\n", err)
|
||||
}
|
||||
|
||||
url := "https://api.github.com/repos/coollabsio/coolify-cli/git/refs/tags"
|
||||
ctx := context.Background()
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", GitHubAPIURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
if resp.StatusCode != 200 {
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("%d - Failed to fetch data from %s. Error: %s", resp.StatusCode, url, string(body))
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
var tags []Tag
|
||||
if err := json.Unmarshal(body, &tags); err != nil {
|
||||
return "", err
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
versionsRaw := make([]string, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
versionStr := tag.Ref[10:]
|
||||
versionsRaw = append(versionsRaw, versionStr)
|
||||
if len(tag.Ref) > 10 {
|
||||
versionStr := tag.Ref[10:]
|
||||
versionsRaw = append(versionsRaw, versionStr)
|
||||
}
|
||||
}
|
||||
|
||||
versions := make([]*compareVersion.Version, len(versionsRaw))
|
||||
for i, raw := range versionsRaw {
|
||||
if len(versionsRaw) == 0 {
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
versions := make([]*compareVersion.Version, 0, len(versionsRaw))
|
||||
for _, raw := range versionsRaw {
|
||||
v, err := compareVersion.NewVersion(raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
continue // Skip invalid versions
|
||||
}
|
||||
versions[i] = v
|
||||
versions = append(versions, v)
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
sort.Sort(compareVersion.Collection(versions))
|
||||
@@ -99,11 +97,11 @@ func CheckLatestVersionOfCli(debug bool) (string, error) {
|
||||
// Compare versions properly using semantic versioning
|
||||
currentVersion, err := compareVersion.NewVersion(GetVersion())
|
||||
if err != nil {
|
||||
return latestVersion.String(), err
|
||||
return "", nil // Silent fail
|
||||
}
|
||||
|
||||
if latestVersion.GreaterThan(currentVersion) {
|
||||
fmt.Printf("There is a new version of Coolify CLI available.\nPlease update with 'coolify update'.\n\n")
|
||||
fmt.Printf("A new version (%s) is available. Update with: coolify update\n", latestVersion.String())
|
||||
}
|
||||
return latestVersion.String(), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetVersion(t *testing.T) {
|
||||
v := GetVersion()
|
||||
if v == "" {
|
||||
t.Error("GetVersion() returned empty string")
|
||||
}
|
||||
// Version should start with 'v'
|
||||
if v[0] != 'v' {
|
||||
t.Errorf("GetVersion() = %q, expected to start with 'v'", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLatestVersionOfCli_UpdateAvailable(t *testing.T) {
|
||||
// Save original values
|
||||
originalURL := GitHubAPIURL
|
||||
originalVersion := version
|
||||
defer func() {
|
||||
GitHubAPIURL = originalURL
|
||||
version = originalVersion
|
||||
}()
|
||||
|
||||
// Set a low version to ensure update is available
|
||||
version = "v0.0.1"
|
||||
|
||||
// Create mock server with newer version
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// Return tags in GitHub API format
|
||||
_, _ = w.Write([]byte(`[{"ref":"refs/tags/v1.0.0"},{"ref":"refs/tags/v2.0.0"}]`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
GitHubAPIURL = server.URL
|
||||
|
||||
// Capture stdout to check for update message
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
latestVersion, err := CheckLatestVersionOfCli(false)
|
||||
|
||||
_ = w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("CheckLatestVersionOfCli() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if latestVersion != "2.0.0" {
|
||||
t.Errorf("CheckLatestVersionOfCli() latestVersion = %q, want %q", latestVersion, "2.0.0")
|
||||
}
|
||||
|
||||
// Should print update message
|
||||
expectedMsg := "A new version (2.0.0) is available. Update with: coolify update\n"
|
||||
if output != expectedMsg {
|
||||
t.Errorf("CheckLatestVersionOfCli() output = %q, want %q", output, expectedMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLatestVersionOfCli_NoUpdate(t *testing.T) {
|
||||
// Save original values
|
||||
originalURL := GitHubAPIURL
|
||||
originalVersion := version
|
||||
defer func() {
|
||||
GitHubAPIURL = originalURL
|
||||
version = originalVersion
|
||||
}()
|
||||
|
||||
// Set a high version to ensure no update is available
|
||||
version = "v99.99.99"
|
||||
|
||||
// Create mock server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`[{"ref":"refs/tags/v1.0.0"},{"ref":"refs/tags/v2.0.0"}]`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
GitHubAPIURL = server.URL
|
||||
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
latestVersion, err := CheckLatestVersionOfCli(false)
|
||||
|
||||
_ = w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("CheckLatestVersionOfCli() error = %v, want nil", err)
|
||||
}
|
||||
|
||||
// Function returns the latest version from GitHub (2.0.0), not the current version
|
||||
if latestVersion != "2.0.0" {
|
||||
t.Errorf("CheckLatestVersionOfCli() latestVersion = %q, want %q", latestVersion, "2.0.0")
|
||||
}
|
||||
|
||||
// Should NOT print any message when already on latest (current v99.99.99 > latest v2.0.0)
|
||||
if output != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() should not print anything when on latest version, got: %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLatestVersionOfCli_APIError_SilentFail(t *testing.T) {
|
||||
// Save original URL
|
||||
originalURL := GitHubAPIURL
|
||||
defer func() {
|
||||
GitHubAPIURL = originalURL
|
||||
}()
|
||||
|
||||
// Create mock server that returns error
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte(`{"error": "internal server error"}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
GitHubAPIURL = server.URL
|
||||
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
latestVersion, err := CheckLatestVersionOfCli(false)
|
||||
|
||||
_ = w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Should return empty string and nil error (silent fail)
|
||||
if err != nil {
|
||||
t.Errorf("CheckLatestVersionOfCli() error = %v, want nil on API error", err)
|
||||
}
|
||||
|
||||
if latestVersion != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() latestVersion = %q, want empty string on API error", latestVersion)
|
||||
}
|
||||
|
||||
// Should NOT print anything on error
|
||||
if output != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() should not print anything on API error, got: %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLatestVersionOfCli_NetworkError_SilentFail(t *testing.T) {
|
||||
// Save original URL
|
||||
originalURL := GitHubAPIURL
|
||||
defer func() {
|
||||
GitHubAPIURL = originalURL
|
||||
}()
|
||||
|
||||
// Use invalid URL to cause network error
|
||||
GitHubAPIURL = "http://localhost:1" // Port 1 should fail to connect
|
||||
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
latestVersion, err := CheckLatestVersionOfCli(false)
|
||||
|
||||
_ = w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, _ = io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Should return empty string and nil error (silent fail)
|
||||
if err != nil {
|
||||
t.Errorf("CheckLatestVersionOfCli() error = %v, want nil on network error", err)
|
||||
}
|
||||
|
||||
if latestVersion != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() latestVersion = %q, want empty string on network error", latestVersion)
|
||||
}
|
||||
|
||||
// Should NOT print anything on error
|
||||
if output != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() should not print anything on network error, got: %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLatestVersionOfCli_InvalidJSON_SilentFail(t *testing.T) {
|
||||
// Save original URL
|
||||
originalURL := GitHubAPIURL
|
||||
defer func() {
|
||||
GitHubAPIURL = originalURL
|
||||
}()
|
||||
|
||||
// Create mock server that returns invalid JSON
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`not valid json`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
GitHubAPIURL = server.URL
|
||||
|
||||
latestVersion, err := CheckLatestVersionOfCli(false)
|
||||
|
||||
// Should return empty string and nil error (silent fail)
|
||||
if err != nil {
|
||||
t.Errorf("CheckLatestVersionOfCli() error = %v, want nil on invalid JSON", err)
|
||||
}
|
||||
|
||||
if latestVersion != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() latestVersion = %q, want empty string on invalid JSON", latestVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLatestVersionOfCli_EmptyTags_SilentFail(t *testing.T) {
|
||||
// Save original URL
|
||||
originalURL := GitHubAPIURL
|
||||
defer func() {
|
||||
GitHubAPIURL = originalURL
|
||||
}()
|
||||
|
||||
// Create mock server that returns empty array
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`[]`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
GitHubAPIURL = server.URL
|
||||
|
||||
latestVersion, err := CheckLatestVersionOfCli(false)
|
||||
|
||||
// Should return empty string and nil error (silent fail)
|
||||
if err != nil {
|
||||
t.Errorf("CheckLatestVersionOfCli() error = %v, want nil on empty tags", err)
|
||||
}
|
||||
|
||||
if latestVersion != "" {
|
||||
t.Errorf("CheckLatestVersionOfCli() latestVersion = %q, want empty string on empty tags", latestVersion)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user