Skip to content

Commit fa1996c

Browse files
committed
Add new storage package
There will be some overlap with storage management (such as from the add command) and nodb and this paves the way to add different storage mechanisms such as Artifactory.
1 parent 0ea15b5 commit fa1996c

File tree

11 files changed

+629
-365
lines changed

11 files changed

+629
-365
lines changed

api/api.go

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

33
import (
4-
"context"
54
"encoding/json"
65
"net/http"
76
"os"
@@ -14,6 +13,7 @@ import (
1413
"github.com/coder/code-marketplace/api/httpapi"
1514
"github.com/coder/code-marketplace/api/httpmw"
1615
"github.com/coder/code-marketplace/database"
16+
"github.com/coder/code-marketplace/storage"
1717
)
1818

1919
// QueryRequest implements an untyped object. It is the data sent to the API to
@@ -54,11 +54,10 @@ type ResultMetadataItem struct {
5454

5555
type Options struct {
5656
Database database.Database
57-
// TODO: Abstract file storage for use with storage services like jFrog.
58-
ExtDir string
59-
Logger slog.Logger
57+
Logger slog.Logger
6058
// Set to <0 to disable.
6159
RateLimit int
60+
Storage storage.Storage
6261
}
6362

6463
type API struct {
@@ -112,12 +111,11 @@ func New(options *Options) *API {
112111
r.Post("/api/extensionquery", api.extensionQuery)
113112

114113
// Endpoint for getting an extension's files or the extension zip.
115-
options.Logger.Info(context.Background(), "Serving files", slog.F("dir", options.ExtDir))
116-
r.Mount("/files", http.StripPrefix("/files", http.FileServer(http.Dir(options.ExtDir))))
114+
r.Mount("/files", http.StripPrefix("/files", options.Storage.FileServer()))
117115

118116
// VS Code can use the files in the response to get file paths but it will
119-
// sometimes ignore that and use use requests to /assets with hardcoded
120-
// types to get files.
117+
// sometimes ignore that and use requests to /assets with hardcoded types to
118+
// get files.
121119
r.Get("/assets/{publisher}/{extension}/{version}/{type}", api.assetRedirect)
122120

123121
// This is the "download manually" URL, which like /assets is hardcoded and
@@ -212,7 +210,7 @@ func (api *API) assetRedirect(rw http.ResponseWriter, r *http.Request) {
212210
baseURL := httpapi.RequestBaseURL(r, "/")
213211
assetType := chi.URLParam(r, "type")
214212
if assetType == "vspackage" {
215-
assetType = database.ExtensionAssetType
213+
assetType = storage.VSIXAssetType
216214
}
217215
url, err := api.Database.GetExtensionAssetPath(r.Context(), &database.Asset{
218216
Extension: chi.URLParam(r, "extension"),

api/api_test.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"net/http/httptest"
1212
"net/url"
1313
"os"
14-
"path/filepath"
1514
"strings"
1615
"testing"
1716

@@ -22,8 +21,29 @@ import (
2221
"github.com/coder/code-marketplace/api"
2322
"github.com/coder/code-marketplace/api/httpapi"
2423
"github.com/coder/code-marketplace/database"
24+
"github.com/coder/code-marketplace/storage"
2525
)
2626

27+
type fakeStorage struct{}
28+
29+
func (s *fakeStorage) FileServer() http.Handler {
30+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
31+
if r.URL.Path == "/nonexistent" {
32+
http.Error(rw, "not found", http.StatusNotFound)
33+
} else {
34+
_, _ = rw.Write([]byte("foobar"))
35+
}
36+
})
37+
}
38+
39+
func (s *fakeStorage) Manifest(ctx context.Context, publisher, extension, version string) (*storage.VSIXManifest, error) {
40+
return nil, errors.New("not implemented")
41+
}
42+
43+
func (s *fakeStorage) WalkExtensions(ctx context.Context, fn func(manifest *storage.VSIXManifest, versions []string) error) error {
44+
return errors.New("not implemented")
45+
}
46+
2747
type fakeDB struct {
2848
exts []*database.Extension
2949
}
@@ -36,7 +56,7 @@ func (db *fakeDB) GetExtensionAssetPath(ctx context.Context, asset *database.Ass
3656
return "", os.ErrNotExist
3757
}
3858
assetPath := "foo"
39-
if asset.Type == database.ExtensionAssetType {
59+
if asset.Type == storage.VSIXAssetType {
4060
assetPath = "extension.vsix"
4161
}
4262
return strings.Join([]string{baseURL.Path, "files", asset.Publisher, asset.Extension, asset.Version, assetPath}, "/"), nil
@@ -252,10 +272,6 @@ func TestAPI(t *testing.T) {
252272
},
253273
}
254274

255-
extdir := t.TempDir()
256-
err := os.WriteFile(filepath.Join(extdir, "exists"), []byte("foobar"), 0o644)
257-
require.NoError(t, err)
258-
259275
for _, c := range cases {
260276
c := c
261277
t.Run(c.Name, func(t *testing.T) {
@@ -264,7 +280,7 @@ func TestAPI(t *testing.T) {
264280
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
265281
apiServer := api.New(&api.Options{
266282
Database: &fakeDB{exts: exts},
267-
ExtDir: extdir,
283+
Storage: &fakeStorage{},
268284
Logger: logger,
269285
})
270286

cli/server.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"net/http"
88
"os/signal"
9+
"path/filepath"
910
"time"
1011

1112
"github.com/spf13/cobra"
@@ -17,6 +18,7 @@ import (
1718

1819
"github.com/coder/code-marketplace/api"
1920
"github.com/coder/code-marketplace/database"
21+
"github.com/coder/code-marketplace/storage"
2022
)
2123

2224
func server() *cobra.Command {
@@ -57,14 +59,28 @@ func server() *cobra.Command {
5759
}
5860
logger.Info(ctx, "Starting API server", slog.F("address", tcpAddr))
5961

60-
// Start the API server.
61-
mapi := api.New(&api.Options{
62-
Database: &database.NoDB{
63-
ExtDir: extdir,
64-
Logger: logger,
65-
},
62+
extdir, err = filepath.Abs(extdir)
63+
if err != nil {
64+
return err
65+
}
66+
67+
// Always local storage for now.
68+
store := &storage.Local{
6669
ExtDir: extdir,
6770
Logger: logger,
71+
}
72+
73+
// Always no database for now.
74+
database := &database.NoDB{
75+
Storage: store,
76+
Logger: logger,
77+
}
78+
79+
// Start the API server.
80+
mapi := api.New(&api.Options{
81+
Database: database,
82+
Storage: store,
83+
Logger: logger,
6884
})
6985
server := &http.Server{
7086
Handler: mapi.Handler,

database/database.go

Lines changed: 0 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,6 @@ type ExtVersion struct {
130130
TargetPlatform string `json:"targetPlatform,omitempty"`
131131
}
132132

133-
const ExtensionAssetType = "Microsoft.VisualStudio.Services.VSIXPackage"
134-
135133
// ExtFile implements IRawGalleryExtensionFile.
136134
// https://github.com/microsoft/vscode/blob/29234f0219bdbf649d6107b18651a1038d6357ac/src/vs/platform/extensionManagement/common/extensionGalleryService.ts#L32-L35
137135
type ExtFile struct {
@@ -153,70 +151,6 @@ type ExtStat struct {
153151
Value float32 `json:"value"`
154152
}
155153

156-
// VSIXManifest implement XMLManifest.PackageManifest.
157-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L9-L26
158-
type VSIXManifest struct {
159-
Metadata VSIXMetadata
160-
Installation struct {
161-
InstallationTarget struct {
162-
ID string `xml:"Id,attr"`
163-
}
164-
}
165-
Dependencies []string
166-
Assets VSIXAssets
167-
}
168-
169-
// VSIXManifest implement XMLManifest.PackageManifest.Metadata.
170-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L11-L22
171-
type VSIXMetadata struct {
172-
Description string
173-
DisplayName string
174-
Identity VSIXIdentity
175-
Tags string
176-
GalleryFlags string
177-
License string
178-
Icon string
179-
Properties VSIXProperties
180-
Categories string
181-
}
182-
183-
// VSIXManifest implement XMLManifest.PackageManifest.Metadata.Identity.
184-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L14
185-
type VSIXIdentity struct {
186-
// ID correlates to ExtensionName, *not* ExtensionID.
187-
ID string `xml:"Id,attr"`
188-
Version string `xml:",attr"`
189-
Publisher string `xml:",attr"`
190-
TargetPlatform string `xml:",attr"`
191-
}
192-
193-
// VSIXProperties implements XMLManifest.PackageManifest.Metadata.Properties.
194-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L19
195-
type VSIXProperties struct {
196-
Property []VSIXProperty
197-
}
198-
199-
// VSIXProperty implements XMLManifest.PackageManifest.Metadata.Properties.Property.
200-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L19
201-
type VSIXProperty struct {
202-
ID string `xml:"Id,attr"`
203-
Value string `xml:",attr"`
204-
}
205-
206-
// VSIXAssets implements XMLManifest.PackageManifest.Assets.
207-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L25
208-
type VSIXAssets struct {
209-
Asset []VSIXAsset
210-
}
211-
212-
// VSIXAsset implements XMLManifest.PackageManifest.Assets.Asset.
213-
// https://github.com/microsoft/vscode-vsce/blob/main/src/xml.ts#L25
214-
type VSIXAsset struct {
215-
Type string `xml:",attr"`
216-
Path string `xml:",attr"`
217-
Addressable string `xml:",attr"`
218-
}
219-
220154
type Asset struct {
221155
Extension string
222156
Publisher string

0 commit comments

Comments
 (0)