Skip to content

Commit 5661942

Browse files
authored
feat: add CLI version cache and proxy support (casdoor#3565)
* feat: add CLI version cache mechanism * feat: add /api/refresh-engines to allowed endpoints in demo mode * feat: add proxy support for cli downloader * feat: add SafeGoroutine for CLIDownloader initialization * refactor: optimize code structure
1 parent 7f9f7c6 commit 5661942

File tree

4 files changed

+103
-17
lines changed

4 files changed

+103
-17
lines changed

authz/authz.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
157157

158158
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
159159
if method == "POST" {
160-
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/verify-code" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") || urlPath == "/api/webhook" || urlPath == "/api/get-qrcode" {
160+
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/verify-code" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") || urlPath == "/api/webhook" || urlPath == "/api/get-qrcode" || urlPath == "/api/refresh-engines" {
161161
return true
162162
} else if urlPath == "/api/update-user" {
163163
// Allow ordinary users to update their own information

controllers/casbin_cli_api.go

+69
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,68 @@ import (
2323
"os/exec"
2424
"sort"
2525
"strings"
26+
"sync"
2627
"time"
2728
)
2829

30+
type CLIVersionInfo struct {
31+
Version string
32+
BinaryPath string
33+
BinaryTime time.Time
34+
}
35+
36+
var (
37+
cliVersionCache = make(map[string]*CLIVersionInfo)
38+
cliVersionMutex sync.RWMutex
39+
)
40+
41+
// getCLIVersion
42+
// @Title getCLIVersion
43+
// @Description Get CLI version with cache mechanism
44+
// @Param language string The language of CLI (go/java/rust etc.)
45+
// @Return string The version string of CLI
46+
// @Return error Error if CLI execution fails
47+
func getCLIVersion(language string) (string, error) {
48+
binaryName := fmt.Sprintf("casbin-%s-cli", language)
49+
50+
binaryPath, err := exec.LookPath(binaryName)
51+
if err != nil {
52+
return "", fmt.Errorf("executable file not found: %v", err)
53+
}
54+
55+
fileInfo, err := os.Stat(binaryPath)
56+
if err != nil {
57+
return "", fmt.Errorf("failed to get binary info: %v", err)
58+
}
59+
60+
cliVersionMutex.RLock()
61+
if info, exists := cliVersionCache[language]; exists {
62+
if info.BinaryPath == binaryPath && info.BinaryTime == fileInfo.ModTime() {
63+
cliVersionMutex.RUnlock()
64+
return info.Version, nil
65+
}
66+
}
67+
cliVersionMutex.RUnlock()
68+
69+
cmd := exec.Command(binaryName, "--version")
70+
output, err := cmd.CombinedOutput()
71+
if err != nil {
72+
return "", fmt.Errorf("failed to get CLI version: %v", err)
73+
}
74+
75+
version := strings.TrimSpace(string(output))
76+
77+
cliVersionMutex.Lock()
78+
cliVersionCache[language] = &CLIVersionInfo{
79+
Version: version,
80+
BinaryPath: binaryPath,
81+
BinaryTime: fileInfo.ModTime(),
82+
}
83+
cliVersionMutex.Unlock()
84+
85+
return version, nil
86+
}
87+
2988
func processArgsToTempFiles(args []string) ([]string, []string, error) {
3089
tempFiles := []string{}
3190
newArgs := []string{}
@@ -93,6 +152,16 @@ func (c *ApiController) RunCasbinCommand() {
93152
return
94153
}
95154

155+
if len(args) > 0 && args[0] == "--version" {
156+
version, err := getCLIVersion(language)
157+
if err != nil {
158+
c.ResponseError(err.Error())
159+
return
160+
}
161+
c.ResponseOk(version)
162+
return
163+
}
164+
96165
tempFiles, processedArgs, err := processArgsToTempFiles(args)
97166
defer func() {
98167
for _, file := range tempFiles {

controllers/cli_downloader.go

+32-15
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import (
99
"encoding/json"
1010
"fmt"
1111
"io"
12-
"net/http"
1312
"os"
1413
"path/filepath"
1514
"runtime"
1615
"strings"
1716
"time"
1817

1918
"github.com/beego/beego"
19+
"github.com/casdoor/casdoor/proxy"
20+
"github.com/casdoor/casdoor/util"
2021
)
2122

2223
const (
@@ -108,9 +109,10 @@ func getFinalBinaryName(lang string) string {
108109
// @Param language string true "Language type"
109110
// @Success 200 {string} string "Download URL and version"
110111
func getLatestCLIURL(repoURL string, language string) (string, string, error) {
111-
resp, err := http.Get(repoURL)
112+
client := proxy.GetHttpClient(repoURL)
113+
resp, err := client.Get(repoURL)
112114
if err != nil {
113-
return "", "", err
115+
return "", "", fmt.Errorf("failed to fetch release info: %v", err)
114116
}
115117
defer resp.Body.Close()
116118

@@ -346,23 +348,36 @@ func downloadCLI() error {
346348
originalPath := filepath.Join(downloadFolder, getBinaryNames()[lang])
347349
fmt.Printf("downloading %s CLI: %s\n", lang, cliURL)
348350

349-
resp, err := http.Get(cliURL)
351+
client := proxy.GetHttpClient(cliURL)
352+
resp, err := client.Get(cliURL)
350353
if err != nil {
351354
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
352355
continue
353356
}
354357

355358
func() {
356359
defer resp.Body.Close()
357-
out, err := os.Create(originalPath)
358-
if err != nil {
359-
fmt.Printf("failed to create %s CLI file: %v\n", lang, err)
360+
361+
if err := os.MkdirAll(filepath.Dir(originalPath), 0o755); err != nil {
362+
fmt.Printf("failed to create directory for %s CLI: %v\n", lang, err)
360363
return
361364
}
362-
defer out.Close()
363365

364-
if _, err = io.Copy(out, resp.Body); err != nil {
365-
fmt.Printf("failed to save %s CLI: %v\n", lang, err)
366+
tmpFile := originalPath + ".tmp"
367+
out, err := os.Create(tmpFile)
368+
if err != nil {
369+
fmt.Printf("failed to create or write %s CLI: %v\n", lang, err)
370+
return
371+
}
372+
defer func() {
373+
out.Close()
374+
os.Remove(tmpFile)
375+
}()
376+
377+
if _, err = io.Copy(out, resp.Body); err != nil ||
378+
out.Close() != nil ||
379+
os.Rename(tmpFile, originalPath) != nil {
380+
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
366381
return
367382
}
368383
}()
@@ -493,10 +508,12 @@ func InitCLIDownloader() {
493508
return
494509
}
495510

496-
err := DownloadCLI()
497-
if err != nil {
498-
fmt.Printf("failed to initialize CLI downloader: %v\n", err)
499-
}
511+
util.SafeGoroutine(func() {
512+
err := DownloadCLI()
513+
if err != nil {
514+
fmt.Printf("failed to initialize CLI downloader: %v\n", err)
515+
}
500516

501-
go ScheduleCLIUpdater()
517+
ScheduleCLIUpdater()
518+
})
502519
}

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func main() {
4646
object.InitCasvisorConfig()
4747

4848
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
49-
controllers.InitCLIDownloader()
49+
util.SafeGoroutine(func() { controllers.InitCLIDownloader() })
5050

5151
// beego.DelStaticPath("/static")
5252
// beego.SetStaticPath("/static", "web/build/static")

0 commit comments

Comments
 (0)