Skip to content

Commit

Permalink
feat: Support for new version detection feature. (#688)
Browse files Browse the repository at this point in the history
Signed-off-by: CFC4N <cfc4n.cs@gmail.com>
  • Loading branch information
cfc4n authored Dec 10, 2024
1 parent 9cf64b4 commit 0131ad5
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 6 deletions.
2 changes: 1 addition & 1 deletion COMPILATION_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,5 @@ CROSS_ARCH=arm64 make

## eBPF学习资料

* [eBPF PDF资料精选](https://github/gojue/ebpf-slide)
* [eBPF PDF资料精选](https://github.com/gojue/ebpf-slide)
* [CFC4N的博客](https://www.cnxct.com)
18 changes: 15 additions & 3 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ const (
)

var (
GitVersion = "v0.0.0_unknow"
// GitVersion default value, eg: linux_arm64:v0.8.10-20241116-fcddaeb:5.15.0-125-generic
GitVersion = "os_arch:v0.0.0-20221111-develop:default_kernel"
//ReleaseDate = "2022-03-16"
)

Expand Down Expand Up @@ -221,7 +222,7 @@ func runModule(modName string, modConfig config.IConfig) {
// listen http server
go func() {
logger.Info().Str("listen", globalConf.Listen).Send()
logger.Info().Msg("https server starting...You can update the configuration file via the HTTP interface.")
logger.Info().Msg("https server starting...You can upgrade the configuration file via the HTTP interface.")
var ec = http.NewHttpServer(globalConf.Listen, reRloadConfig, logger)
err = ec.Run()
if err != nil {
Expand All @@ -230,6 +231,18 @@ func runModule(modName string, modConfig config.IConfig) {
}
}()

ctx, cancelFun := context.WithCancel(context.TODO())

// upgrade check
go func() {
tags, url, e := upgradeCheck(ctx)
if e != nil {
logger.Debug().Msgf("upgrade check failed: %v", e)
return
}
logger.Warn().Msgf("A new version %s is available:%s", tags, url)
}()

// run module
{
// config check
Expand All @@ -245,7 +258,6 @@ func runModule(modName string, modConfig config.IConfig) {
reload:
// 初始化
mod := modFunc()
ctx, cancelFun := context.WithCancel(context.TODO())
err = mod.Init(ctx, &logger, modConfig, ecw)
if err != nil {
logger.Fatal().Err(err).Bool("isReload", isReload).Msg("module initialization failed")
Expand Down
78 changes: 78 additions & 0 deletions cli/cmd/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package cmd

import (
"context"
"errors"
"fmt"
"github.com/gojue/ecapture/pkg/upgrade"
"golang.org/x/sys/unix"
"regexp"
"strings"
)

const urlReleases = "https://api.github.com/repos/gojue"
const urlReleasesCN = "https://image.cnxct.com"
const apiReleases string = "/ecapture/releases/latest"

var (
ErrOsArchNotFound = errors.New("new tag found, but no os/arch match")
ErrAheadOfLatestVersion = errors.New("local version is ahead of latest version")
)

func upgradeCheck(ctx context.Context) (string, string, error) {

// uname -a
var uname unix.Utsname
err := unix.Uname(&uname)
if err != nil {
return "", "", fmt.Errorf("Error getting uname: %v", err)
}
var useragent = fmt.Sprintf("eCapture Cli (%s %s %s)",
byteToString(uname.Sysname[:]), // 系统名称
byteToString(uname.Release[:]), // 版本号
byteToString(uname.Machine[:]), // 机器类型
)
var arch = "amd64"
if byteToString(uname.Machine[:]) == "aarch64" {
arch = "arm64"
}
rex := regexp.MustCompile(`([^:]+):v?(\d+\.\d+\.\d+)[^:]+:[^:]+`)
verMatch := rex.FindStringSubmatch(GitVersion)
if len(verMatch) <= 2 {
return "", "", fmt.Errorf("error matching version: %s, verMatch:%v", GitVersion, verMatch)
}
var os = "linux"
if strings.Contains(verMatch[1], "androidgki") {
os = "android"
}
githubResp, err := upgrade.GetLatestVersion(useragent, fmt.Sprintf("%s%s?ver=%s", urlReleasesCN, apiReleases, GitVersion), ctx)
if err != nil {
return "", "", fmt.Errorf("error getting latest version: %v", err)
}

comp, err := upgrade.CheckVersion(verMatch[2], githubResp.TagName)
if err != nil {
return "", "", fmt.Errorf("error checking version: %v", err)
}

if comp >= 0 {
return "", "", ErrAheadOfLatestVersion
}

// "name": "ecapture-v0.8.12-android-amd64.tar.gz",
var targetAsset = fmt.Sprintf("ecapture-%s-%s-%s.tar.gz", githubResp.TagName, os, arch)
for _, asset := range githubResp.Assets {
if asset.Name == targetAsset {
return githubResp.TagName, asset.BrowserDownloadURL, nil
}
}
return "", "", ErrOsArchNotFound
}

func byteToString(b []byte) string {
n := 0
for n < len(b) && b[n] != 0 {
n++
}
return string(b[:n])
}
84 changes: 84 additions & 0 deletions pkg/upgrade/github_response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2024 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package upgrade

import (
"time"
)

// from https://api.github.com/repos/gojue/ecapture/releases/latest
// https://developer.github.com/v3/repos/releases/

// GithubReleaseResp data related to a release.
type GithubReleaseResp struct {
URL string `json:"url"`
AssetsURL string `json:"assets_url"`
UploadURL string `json:"upload_url"`
HTMLURL string `json:"html_url"`
ID int64 `json:"id"`
Author Author `json:"author"`
NodeID string `json:"node_id"`
TagName string `json:"tag_name"`
TargetCommitish string `json:"target_commitish"`
Name string `json:"name"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
CreatedAt time.Time `json:"created_at"`
PublishedAt time.Time `json:"published_at"`
Assets []ReleaseAsset `json:"assets"`
TarballURL string `json:"tarball_url"`
ZipballURL string `json:"zipball_url"`
Body string `json:"body"`
MentionsCount int `json:"mentions_count"`
}

type Author struct {
Login string `json:"login"`
ID int64 `json:"id"`
NodeID string `json:"node_id"`
AvatarURL string `json:"avatar_url"`
GravatarID string `json:"gravatar_id"`
URL string `json:"url"`
HTMLURL string `json:"html_url"`
FollowersURL string `json:"followers_url"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
OrganizationsURL string `json:"organizations_url"`
ReposURL string `json:"repos_url"`
EventsURL string `json:"events_url"`
ReceivedEventsURL string `json:"received_events_url"`
Type string `json:"type"`
UserViewType string `json:"user_view_type"`
SiteAdmin bool `json:"site_admin"`
}

// ReleaseAsset data related to a release asset.
type ReleaseAsset struct {
URL string `json:"url"`
ID int64 `json:"id"`
NodeID string `json:"node_id"`
Name string `json:"name"`
Label string `json:"label"`
Uploader Author `json:"uploader"`
ContentType string `json:"content_type"`
State string `json:"state"`
Size int64 `json:"size"`
DownloadCount int `json:"download_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
BrowserDownloadURL string `json:"browser_download_url"`
}
150 changes: 150 additions & 0 deletions pkg/upgrade/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright 2024 CFC4N <cfc4n.cs@gmail.com>. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package upgrade

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)

/*
https://api.github.com/repos/gojue/ecapture/releases/latest
https://api.github.com/repos/gojue/ecapture/releases/tags/v0.1.0
https://github.com/gojue/ecapture/releases/download/v0.8.12/checksum-v0.8.12.txt
image.cnxct.com/ecapture/releases/latest
image.cnxct.com/ecapture/releases/tags/v0.1.0
image.cnxct.com/ecapture/download/v0.8.12/checksum-v0.8.12.txt
*/

// we use the GitHub REST V3 API as no login is required
// https://docs.github.com/zh/rest/using-the-rest-api

func GetLatestVersion(ua, url string, ctx context.Context) (GithubReleaseResp, error) {
var release GithubReleaseResp
err := makeGithubRequest(ctx, ua, url, &release)
if err != nil {
return release, err
}

return release, nil

}

func CheckVersion(localVer, remoteVer string) (int, error) {

localVer = strings.ReplaceAll(localVer, "v", "")
remoteVer = strings.ReplaceAll(remoteVer, "v", "")

v1, err := ParseVersion(localVer)
if err != nil {
return 0, err
}

v2, err := ParseVersion(remoteVer)
if err != nil {
return 0, err
}

comparison := CompareVersions(v1, v2)
return comparison, nil
}

func makeGithubRequest(ctx context.Context, ua, url string, output interface{}) error {
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}

client := &http.Client{
Timeout: 3 * time.Second,
Transport: transport,
}

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)

req.Header.Add("Accept", "application/json") // gh api recommendation , send header with api version
req.Header.Set("User-Agent", ua) // eCapture Cli Linux 5.15.0-125-generic aarch64
response, err := client.Do(req)
if err != nil {
//lint:ignore ST1005 Github is a proper capitalized noun
return fmt.Errorf("API request failed: %w", err)
}

if response.StatusCode != http.StatusOK {
//lint:ignore ST1005 Github is a proper capitalized noun
return fmt.Errorf("API request failed, statusCOde: %s", response.Status)
}

defer response.Body.Close()

data, err := io.ReadAll(response.Body)
if err != nil {
//lint:ignore ST1005 Github is a proper capitalized noun
return fmt.Errorf("API read response failed: %w", err)
}

err = json.Unmarshal(data, output)
if err != nil {
return fmt.Errorf("unmarshalling Github API response failed: %w", err)
}

return nil
}

// Version 结构体表示一个版本号
type Version struct {
Major int
Minor int
Patch int
}

// ParseVersion 解析版本号字符串
func ParseVersion(versionStr string) (Version, error) {
parts := strings.Split(versionStr, ".")
if len(parts) != 3 {
return Version{}, fmt.Errorf("invalid version format")
}

major, err := strconv.Atoi(parts[0])
if err != nil {
return Version{}, err
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return Version{}, err
}
patch, err := strconv.Atoi(parts[2])
if err != nil {
return Version{}, err
}

return Version{Major: major, Minor: minor, Patch: patch}, nil
}

// CompareVersions 比较两个版本号
func CompareVersions(v1, v2 Version) int {
if v1.Major != v2.Major {
return v1.Major - v2.Major
}
if v1.Minor != v2.Minor {
return v1.Minor - v2.Minor
}
return v1.Patch - v2.Patch
}
Loading

0 comments on commit 0131ad5

Please sign in to comment.