Skip to content

Commit d585933

Browse files
authored
{Core} Add aliyun upgrade and configurable plugin index source and fix CloudSSO/OAuth one-off CLI flags persisting (#1299)
* add aliyun upgrade * adjust * fix tests * add tests * add plugin setting * add file * fix tests * add tests * add tests * add tests * add tests * fix oauth cloudsso profile saving
1 parent a38bc06 commit d585933

14 files changed

Lines changed: 2160 additions & 17 deletions

cli/plugin/manager.go

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,23 @@ import (
1010
"fmt"
1111
"io"
1212
"net/http"
13+
"net/url"
1314
"os"
15+
"path"
1416
"path/filepath"
1517
"runtime"
1618
"sort"
1719
"strings"
1820
"time"
1921

2022
"github.com/aliyun/aliyun-cli/v3/cli"
23+
"github.com/aliyun/aliyun-cli/v3/sysconfig/pluginsettings"
2124
"golang.org/x/mod/semver"
2225
)
2326

2427
const (
25-
IndexURL = "https://aliyun-cli-pub.oss-cn-hangzhou.aliyuncs.com/plugins/plugin_pkg_index.json" // 默认索引地址
26-
CommandIndexURL = "https://aliyun-cli-pub.oss-cn-hangzhou.aliyuncs.com/plugins/plugin_search_index.json" // 命令倒排索引地址
28+
IndexURL = "https://aliyuncli.alicdn.com/plugins/plugin_pkg_index.json" // 默认索引地址
29+
CommandIndexURL = "https://aliyuncli.alicdn.com/plugins/plugin_search_index.json" // 命令倒排索引地址
2730
EnvPluginsDir = "ALIBABA_CLOUD_CLI_PLUGINS_DIR"
2831
EnvNoCache = "ALIBABA_CLOUD_CLI_PLUGIN_NO_CACHE"
2932

@@ -43,8 +46,9 @@ func (e *ErrPluginNotFound) Error() string {
4346

4447
type Manager struct {
4548
rootDir string
46-
indexURL string // For testing: allows overriding IndexURL
47-
commandIndexURL string // For testing: allows overriding CommandIndexURL
49+
sourceBase string // from plugin-settings.json / env; empty = use built-in index URLs
50+
indexURL string // For testing: allows overriding resolved package index URL
51+
commandIndexURL string // For testing: allows overriding resolved command index URL
4852
}
4953

5054
func getHomePath() string {
@@ -86,7 +90,50 @@ func NewManager() (*Manager, error) {
8690
if err := os.MkdirAll(rootDir, 0755); err != nil {
8791
return nil, err
8892
}
89-
return &Manager{rootDir: rootDir}, nil
93+
sysDir := filepath.Join(getHomePath(), ".aliyun")
94+
settings, err := pluginsettings.Load(sysDir)
95+
if err != nil {
96+
settings = pluginsettings.Default()
97+
}
98+
base := pluginsettings.EffectiveSourceBase(settings)
99+
return &Manager{rootDir: rootDir, sourceBase: base}, nil
100+
}
101+
102+
func (m *Manager) resolvedPkgIndexURL() string {
103+
if m.indexURL != "" {
104+
return m.indexURL
105+
}
106+
if b := strings.TrimRight(strings.TrimSpace(m.sourceBase), "/"); b != "" {
107+
return b + "/plugin_pkg_index.json"
108+
}
109+
return IndexURL
110+
}
111+
112+
func (m *Manager) resolvedCommandIndexURL() string {
113+
if m.commandIndexURL != "" {
114+
return m.commandIndexURL
115+
}
116+
if b := strings.TrimRight(strings.TrimSpace(m.sourceBase), "/"); b != "" {
117+
return b + "/plugin_search_index.json"
118+
}
119+
return CommandIndexURL
120+
}
121+
122+
// common layout: .../pkgs/{name}/{version}/{basename}.
123+
func (m *Manager) resolvePackageDownloadURL(origURL, pluginName, version string) string {
124+
if strings.TrimSpace(m.sourceBase) == "" {
125+
return origURL
126+
}
127+
u, err := url.Parse(origURL)
128+
if err != nil {
129+
return origURL
130+
}
131+
baseName := path.Base(u.Path)
132+
if baseName == "" || baseName == "." || baseName == "/" {
133+
return origURL
134+
}
135+
b := strings.TrimRight(strings.TrimSpace(m.sourceBase), "/")
136+
return fmt.Sprintf("%s/pkgs/%s/%s/%s", b, pluginName, version, baseName)
90137
}
91138

92139
func (m *Manager) readCache(cacheFile string, ttl time.Duration, result interface{}) (hit bool, staleAvailable bool) {
@@ -161,10 +208,7 @@ func (m *Manager) fetchWithCache(url, cacheFile string, result interface{}) erro
161208
}
162209

163210
func (m *Manager) GetIndex() (*Index, error) {
164-
indexURL := IndexURL
165-
if m.indexURL != "" {
166-
indexURL = m.indexURL
167-
}
211+
indexURL := m.resolvedPkgIndexURL()
168212
cacheFile := filepath.Join(m.rootDir, indexCacheFile)
169213
var index Index
170214
if err := m.fetchWithCache(indexURL, cacheFile, &index); err != nil {
@@ -174,10 +218,7 @@ func (m *Manager) GetIndex() (*Index, error) {
174218
}
175219

176220
func (m *Manager) GetCommandIndex() (*CommandIndex, error) {
177-
commandIndexURL := CommandIndexURL
178-
if m.commandIndexURL != "" {
179-
commandIndexURL = m.commandIndexURL
180-
}
221+
commandIndexURL := m.resolvedCommandIndexURL()
181222
cacheFile := filepath.Join(m.rootDir, commandCacheFile)
182223
var index CommandIndex
183224
if err := m.fetchWithCache(commandIndexURL, cacheFile, &index); err != nil {
@@ -678,14 +719,18 @@ func (m *Manager) installPlugin(ctx *cli.Context, targetPlugin *PluginInfo, vers
678719
return err
679720
}
680721

681-
archivePath, err := m.downloadAndVerifyPlugin(ctx, platInfo, actualPluginName, version)
722+
downloadURL := m.resolvePackageDownloadURL(platInfo.URL, actualPluginName, version)
723+
platForDownload := *platInfo
724+
platForDownload.URL = downloadURL
725+
726+
archivePath, err := m.downloadAndVerifyPlugin(ctx, &platForDownload, actualPluginName, version)
682727
if err != nil {
683728
return err
684729
}
685730
defer os.RemoveAll(filepath.Dir(archivePath))
686731

687732
extractDir := filepath.Join(m.rootDir, actualPluginName)
688-
if err := m.extractPlugin(archivePath, extractDir, platInfo.URL); err != nil {
733+
if err := m.extractPlugin(archivePath, extractDir, downloadURL); err != nil {
689734
return err
690735
}
691736

cli/plugin/manager_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"time"
2222

2323
"github.com/aliyun/aliyun-cli/v3/cli"
24+
"github.com/aliyun/aliyun-cli/v3/sysconfig/pluginsettings"
2425
"github.com/stretchr/testify/assert"
2526
)
2627

@@ -30,6 +31,35 @@ func newTestContext() *cli.Context {
3031
return cli.NewCommandContext(stdout, stderr)
3132
}
3233

34+
func TestManager_resolvedIndexURLsWithSourceBase(t *testing.T) {
35+
m := &Manager{sourceBase: "https://mirror.example.com/plugins"}
36+
assert.Equal(t, "https://mirror.example.com/plugins/plugin_pkg_index.json", m.resolvedPkgIndexURL())
37+
assert.Equal(t, "https://mirror.example.com/plugins/plugin_search_index.json", m.resolvedCommandIndexURL())
38+
}
39+
40+
func TestManager_resolvePackageDownloadURL(t *testing.T) {
41+
orig := "https://aliyun-cli-pub.oss-cn-hangzhou.aliyuncs.com/plugins/pkgs/aliyun-cli-acc/0.2.0/aliyun-cli-acc-linux-amd64.tar.gz"
42+
m := &Manager{sourceBase: "https://mirror.example.com/plugins"}
43+
got := m.resolvePackageDownloadURL(orig, "aliyun-cli-acc", "0.2.0")
44+
assert.Equal(t, "https://mirror.example.com/plugins/pkgs/aliyun-cli-acc/0.2.0/aliyun-cli-acc-linux-amd64.tar.gz", got)
45+
46+
m2 := &Manager{}
47+
assert.Equal(t, orig, m2.resolvePackageDownloadURL(orig, "x", "1.0.0"))
48+
}
49+
50+
func TestNewManager_LoadsSourceBaseFromFile(t *testing.T) {
51+
home := t.TempDir()
52+
cleanup := setTestHomeDir(t, home)
53+
defer cleanup()
54+
confDir := filepath.Join(home, ".aliyun")
55+
assert.NoError(t, os.MkdirAll(confDir, 0755))
56+
data := []byte(`{"source_base":"https://x.example/plugins"}`)
57+
assert.NoError(t, os.WriteFile(filepath.Join(confDir, pluginsettings.ConfigFileName), data, 0600))
58+
mgr, err := NewManager()
59+
assert.NoError(t, err)
60+
assert.Equal(t, "https://x.example/plugins", mgr.sourceBase)
61+
}
62+
3363
func TestNewManager(t *testing.T) {
3464
t.Run("Success", func(t *testing.T) {
3565
testHome := t.TempDir()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2009-present, Alibaba Cloud All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//go:build windows
16+
17+
package upgrade
18+
19+
import (
20+
"os"
21+
"path/filepath"
22+
"testing"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestReplaceBinary_Windows_NoOldFileAfterSuccess(t *testing.T) {
28+
tmpDir := t.TempDir()
29+
current := filepath.Join(tmpDir, "aliyun.exe")
30+
oldSidecar := current + ".old"
31+
newPath := filepath.Join(tmpDir, "aliyun.new.exe")
32+
want := []byte("new payload")
33+
os.WriteFile(current, []byte("old"), 0755)
34+
os.WriteFile(newPath, want, 0755)
35+
36+
assert.NoError(t, replaceBinary(newPath, current))
37+
38+
got, err := os.ReadFile(current)
39+
assert.NoError(t, err)
40+
assert.Equal(t, want, got)
41+
42+
_, err = os.Stat(oldSidecar)
43+
assert.True(t, os.IsNotExist(err), ".old sidecar should be removed after successful replace")
44+
}

0 commit comments

Comments
 (0)