Skip to content

Commit a76b2aa

Browse files
authored
feat(vscode): Use openvsx (#174)
* feat(vscode): Use openvsx Signed-off-by: Ce Gao <cegao@tensorchord.ai> * fix: Check cache first Signed-off-by: Ce Gao <cegao@tensorchord.ai> * fix: Remove version in README Signed-off-by: Ce Gao <cegao@tensorchord.ai>
1 parent 0968f83 commit a76b2aa

6 files changed

Lines changed: 178 additions & 54 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Checkout the [examples](./examples/mnist), and configure envd with the manifest
4747

4848
```python
4949
vscode(plugins=[
50-
"ms-python.python-2021.12.1559732655",
50+
"ms-python.python",
5151
])
5252

5353
base(os="ubuntu20.04", language="python3")

pkg/editor/vscode/types.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,26 @@ package vscode
1717
import "fmt"
1818

1919
const (
20-
vscodePackageURLTemplate = "https://%s.gallery.vsassets.io/_apis/public/gallery/publisher/%s/extension/%s/%s/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage"
20+
vendorVSCodeTemplate = "https://%s.gallery.vsassets.io/_apis/public/gallery/publisher/%s/extension/%s/%s/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage"
21+
vendorOpenVSXTemplate = "https://open-vsx.org/api/%s/%s/latest"
22+
)
23+
24+
type MarketplaceVendor string
25+
26+
const (
27+
MarketplaceVendorVSCode MarketplaceVendor = "vscode"
28+
MarketplaceVendorOpenVSX MarketplaceVendor = "openvsx"
2129
)
2230

2331
type Plugin struct {
2432
Publisher string
2533
Extension string
26-
Version string
34+
Version *string
2735
}
2836

2937
func (p Plugin) String() string {
30-
return fmt.Sprintf("%s.%s-%s", p.Publisher, p.Extension, p.Version)
38+
if p.Version != nil {
39+
return fmt.Sprintf("%s.%s-%s", p.Publisher, p.Extension, *p.Version)
40+
}
41+
return fmt.Sprintf("%s.%s", p.Publisher, p.Extension)
3142
}

pkg/editor/vscode/util.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package vscode
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/cockroachdb/errors"
11+
"github.com/sirupsen/logrus"
12+
)
13+
14+
func GetLatestVersionURL(p Plugin) (string, error) {
15+
// Auto-detect the version.
16+
// Refer to https://github.com/tensorchord/envd/issues/161#issuecomment-1129475975
17+
latestURL := fmt.Sprintf(vendorOpenVSXTemplate, p.Publisher, p.Extension)
18+
resp, err := http.Get(latestURL)
19+
if err != nil {
20+
return "", errors.Wrap(err, "failed to get latest version")
21+
}
22+
defer resp.Body.Close()
23+
if resp.StatusCode != http.StatusOK {
24+
return "", errors.Errorf("failed to get latest version: %s", resp.Status)
25+
}
26+
jsonResp := make(map[string]interface{})
27+
if err := json.NewDecoder(resp.Body).Decode(&jsonResp); err != nil {
28+
return "", errors.Wrap(err, "failed to decode response")
29+
}
30+
if jsonResp["files"] == nil {
31+
return "", errors.New("failed to get latest version: no files")
32+
}
33+
files := jsonResp["files"].(map[string]interface{})
34+
if files["download"] == nil {
35+
return "", errors.New("failed to get latest version: no download url")
36+
}
37+
return files["download"].(string), nil
38+
}
39+
40+
func ParsePlugin(p string) (*Plugin, error) {
41+
indexPublisher := strings.Index(p, ".")
42+
if indexPublisher == -1 {
43+
return nil, errors.New("invalid publisher")
44+
}
45+
publisher := p[:indexPublisher]
46+
47+
indexExtension := strings.LastIndex(p[indexPublisher:], "-")
48+
if indexExtension == -1 {
49+
extension := p[indexPublisher+1:]
50+
logrus.WithFields(logrus.Fields{
51+
"publisher": publisher,
52+
"extension": extension,
53+
}).Debug("vscode plugin is parsed without version")
54+
return &Plugin{
55+
Publisher: publisher,
56+
Extension: extension,
57+
}, nil
58+
}
59+
60+
indexExtension = indexPublisher + indexExtension
61+
extension := p[indexPublisher+1 : indexExtension]
62+
version := p[indexExtension+1:]
63+
if _, err := strconv.Atoi(version[0:1]); err != nil {
64+
extension := p[indexPublisher+1:]
65+
logrus.WithFields(logrus.Fields{
66+
"publisher": publisher,
67+
"extension": extension,
68+
}).Debug("vscode plugin is parsed without version")
69+
return &Plugin{
70+
Publisher: publisher,
71+
Extension: extension,
72+
}, nil
73+
}
74+
logrus.WithFields(logrus.Fields{
75+
"publisher": publisher,
76+
"extension": extension,
77+
"version": version,
78+
}).Debug("vscode plugin is parsed")
79+
return &Plugin{
80+
Publisher: publisher,
81+
Extension: extension,
82+
Version: &version,
83+
}, nil
84+
}

pkg/editor/vscode/vscode_test.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ import (
2020

2121
var _ = Describe("Visual Studio Code", func() {
2222
Describe("Plugin", func() {
23+
It("should get the latest version successfully", func() {
24+
url, err := GetLatestVersionURL(Plugin{
25+
Publisher: "redhat",
26+
Extension: "java",
27+
})
28+
Expect(err).To(BeNil())
29+
Expect(url).NotTo(Equal(""))
30+
})
2331
It("should be able to parse", func() {
2432
tcs := []struct {
2533
name string
@@ -57,11 +65,14 @@ var _ = Describe("Visual Studio Code", func() {
5765
expectedErr: false,
5866
},
5967
{
60-
name: "test",
61-
expectedErr: true,
68+
name: "dbaeumer.vscode-eslint",
69+
expectedPublisher: "dbaeumer",
70+
expectedExtension: "vscode-eslint",
71+
expectedVersion: "",
72+
expectedErr: false,
6273
},
6374
{
64-
name: "test.test",
75+
name: "test",
6576
expectedErr: true,
6677
},
6778
}
@@ -73,7 +84,10 @@ var _ = Describe("Visual Studio Code", func() {
7384
Expect(err).ToNot(HaveOccurred())
7485
Expect(p.Publisher).To(Equal(tc.expectedPublisher))
7586
Expect(p.Extension).To(Equal(tc.expectedExtension))
76-
Expect(p.Version).To(Equal(tc.expectedVersion))
87+
if tc.expectedVersion != "" {
88+
Expect(p.Version).NotTo(BeNil())
89+
Expect(*p.Version).To(Equal(tc.expectedVersion))
90+
}
7791
}
7892
}
7993
})

pkg/editor/vscode/vsocde.go

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"io"
2020
"net/http"
2121
"os"
22-
"strings"
2322

2423
"github.com/cockroachdb/errors"
2524
"github.com/sirupsen/logrus"
@@ -38,29 +37,75 @@ type Client interface {
3837
}
3938

4039
type generalClient struct {
40+
vendor MarketplaceVendor
41+
logger *logrus.Entry
4142
}
4243

43-
func NewClient() Client {
44-
return &generalClient{}
44+
func NewClient(vendor MarketplaceVendor) (Client, error) {
45+
switch vendor {
46+
case MarketplaceVendorOpenVSX:
47+
return &generalClient{
48+
vendor: vendor,
49+
logger: logrus.WithField("vendor", MarketplaceVendorOpenVSX),
50+
}, nil
51+
case MarketplaceVendorVSCode:
52+
return &generalClient{
53+
vendor: vendor,
54+
logger: logrus.WithField("vendor", MarketplaceVendorVSCode),
55+
}, nil
56+
default:
57+
return nil, errors.Errorf("unknown marketplace vendor %s", vendor)
58+
}
4559
}
4660

4761
func (c generalClient) PluginPath(p Plugin) string {
48-
return fmt.Sprintf("%s.%s-%s/extension/", p.Publisher, p.Extension, p.Version)
62+
if p.Version != nil {
63+
return fmt.Sprintf("%s.%s-%s/extension/", p.Publisher, p.Extension, *p.Version)
64+
65+
}
66+
return fmt.Sprintf("%s.%s/extension/", p.Publisher, p.Extension)
4967
}
5068

5169
func unzipPath(p Plugin) string {
52-
return fmt.Sprintf("%s/%s.%s-%s", home.GetManager().CacheDir(),
53-
p.Publisher, p.Extension, p.Version)
70+
if p.Version != nil {
71+
return fmt.Sprintf("%s/%s.%s-%s", home.GetManager().CacheDir(),
72+
p.Publisher, p.Extension, *p.Version)
73+
}
74+
return fmt.Sprintf("%s/%s.%s", home.GetManager().CacheDir(),
75+
p.Publisher, p.Extension)
5476
}
5577

5678
// DownloadOrCache downloads or cache the plugin.
5779
// If the plugin is already downloaded, it returns true.
5880
func (c generalClient) DownloadOrCache(p Plugin) (bool, error) {
59-
url := fmt.Sprintf(vscodePackageURLTemplate,
60-
p.Publisher, p.Publisher, p.Extension, p.Version)
81+
cacheKey := fmt.Sprintf("%s-%s", cachekeyPrefix, p)
82+
if home.GetManager().Cached(cacheKey) {
83+
logrus.WithFields(logrus.Fields{
84+
"cache": cacheKey,
85+
}).Debugf("vscode plugin %s already exists in cache", p)
86+
return true, nil
87+
}
88+
89+
var url, filename string
90+
if c.vendor == MarketplaceVendorVSCode {
91+
if p.Version == nil {
92+
return false, errors.New("version is required for vscode marketplace")
93+
}
94+
// TODO(gaocegege): Support version auto-detection.
95+
url = fmt.Sprintf(vendorVSCodeTemplate,
96+
p.Publisher, p.Publisher, p.Extension, *p.Version)
97+
filename = fmt.Sprintf("%s/%s.%s-%s.vsix", home.GetManager().CacheDir(),
98+
p.Publisher, p.Extension, *p.Version)
99+
} else {
100+
var err error
101+
url, err = GetLatestVersionURL(p)
102+
if err != nil {
103+
return false, errors.Wrap(err, "failed to get latest version url")
104+
}
105+
filename = fmt.Sprintf("%s/%s.%s.vsix", home.GetManager().CacheDir(),
106+
p.Publisher, p.Extension)
107+
}
61108

62-
filename := fmt.Sprintf("%s/%s.%s-%s.vsix", home.GetManager().CacheDir(),
63-
p.Publisher, p.Extension, p.Version)
64109
logger := logrus.WithFields(logrus.Fields{
65110
"publisher": p.Publisher,
66111
"extension": p.Extension,
@@ -69,14 +114,6 @@ func (c generalClient) DownloadOrCache(p Plugin) (bool, error) {
69114
"file": filename,
70115
})
71116

72-
cacheKey := fmt.Sprintf("%s-%s", cachekeyPrefix, p)
73-
if home.GetManager().Cached(cacheKey) {
74-
logger.WithFields(logrus.Fields{
75-
"cache": cacheKey,
76-
}).Debugf("vscode plugin %s already exists in cache", p)
77-
return true, nil
78-
}
79-
80117
logger.Debugf("downloading vscode plugin %s", p)
81118
out, err := os.Create(filename)
82119

@@ -107,30 +144,3 @@ func (c generalClient) DownloadOrCache(p Plugin) (bool, error) {
107144
}
108145
return false, nil
109146
}
110-
111-
func ParsePlugin(p string) (*Plugin, error) {
112-
indexPublisher := strings.Index(p, ".")
113-
if indexPublisher == -1 {
114-
return nil, errors.New("invalid publisher")
115-
}
116-
publisher := p[:indexPublisher]
117-
118-
indexExtension := strings.LastIndex(p[indexPublisher:], "-")
119-
if indexExtension == -1 {
120-
return nil, errors.New("invalid extension")
121-
}
122-
123-
indexExtension = indexPublisher + indexExtension
124-
extension := p[indexPublisher+1 : indexExtension]
125-
version := p[indexExtension+1:]
126-
logrus.WithFields(logrus.Fields{
127-
"publisher": publisher,
128-
"extension": extension,
129-
"version": version,
130-
}).Debug("vscode plugin is parsed")
131-
return &Plugin{
132-
Publisher: publisher,
133-
Extension: extension,
134-
Version: version,
135-
}, nil
136-
}

pkg/lang/ir/editor.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package ir
22

33
import (
4+
"github.com/cockroachdb/errors"
45
"github.com/moby/buildkit/client/llb"
6+
57
"github.com/tensorchord/envd/pkg/editor/vscode"
68
"github.com/tensorchord/envd/pkg/flag"
79
"github.com/tensorchord/envd/pkg/progress/compileui"
@@ -13,7 +15,10 @@ func (g Graph) compileVSCode() (*llb.State, error) {
1315
}
1416
inputs := []llb.State{}
1517
for _, p := range g.VSCodePlugins {
16-
vscodeClient := vscode.NewClient()
18+
vscodeClient, err := vscode.NewClient(vscode.MarketplaceVendorOpenVSX)
19+
if err != nil {
20+
return nil, errors.Wrap(err, "failed to create vscode client")
21+
}
1722
g.Writer.LogVSCodePlugin(p, compileui.ActionStart, false)
1823
if cached, err := vscodeClient.DownloadOrCache(p); err != nil {
1924
return nil, err

0 commit comments

Comments
 (0)