Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
27ac9c7
[dashboard] allo to install additional editor image
akosyakov Apr 3, 2023
e43e8a7
[ide-service] resolve user options
akosyakov Apr 3, 2023
a1c041d
[ide-service] fix id computation
akosyakov Apr 3, 2023
e4dcc1b
[ide-service] ide manifest
akosyakov Apr 3, 2023
23399d2
[supervisor] fix starting IDE without service
akosyakov Apr 3, 2023
e054f65
[dashboard] refresh ide options on image install
akosyakov Apr 3, 2023
a572c6a
[dashboard] add a hook to delete editor ref
akosyakov Apr 3, 2023
1194b92
[blobserve] allow any repo
akosyakov Apr 3, 2023
0a3bed0
implement delete
akosyakov Apr 3, 2023
86fb387
[ide-service] fix tests
akosyakov Apr 3, 2023
28d2c21
protocol: fix typings
akosyakov Apr 4, 2023
ac13c46
fix delete button
akosyakov Apr 4, 2023
765c91e
change delete to uninstall
akosyakov Apr 4, 2023
791fc14
actually fix uninstall button
akosyakov Apr 4, 2023
b737985
[server] provide all user settigns
akosyakov Apr 4, 2023
c510b9b
[ide-service] respect custom editors while starting a workspace
akosyakov Apr 4, 2023
f84dc71
[blobserve] support IDE manifest
akosyakov Apr 4, 2023
087996a
blobserve: fix workdir for desktop
akosyakov Apr 5, 2023
e22832c
add jetbrains client temporary
akosyakov Apr 5, 2023
072302e
[ide-service] handle client without default IDE
akosyakov Apr 5, 2023
b9be4fd
[ide-service] cache ide manifest
akosyakov Apr 5, 2023
2139192
[ide-service] fix verison resolver tests
akosyakov Apr 5, 2023
ed6a09d
[ide-service] cache image resolution too
akosyakov Apr 5, 2023
4443a00
[ide-service] support latest channel
akosyakov Apr 5, 2023
88b187d
allow to provie installation steps
akosyakov Apr 10, 2023
02c3492
fix dompurify typings
akosyakov Apr 11, 2023
696e48b
remove jetbrains client
akosyakov Apr 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions components/blobserve/pkg/blobserve/blobserve.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,17 @@ func (reg *Server) serve(w http.ResponseWriter, req *http.Request) {

// The blobFor operation's context must be independent of this request. Even if we do not
// serve this request in time, we might want to serve another from the same ref in the future.
blobFS, hash, err := reg.refstore.BlobFor(context.Background(), ref, false)
blobFS, hash, blobWorkDir, err := reg.refstore.BlobFor(context.Background(), ref, false)
if err == errdefs.ErrNotFound {
http.Error(w, fmt.Sprintf("image %s not found: %q", html.EscapeString(ref), err), http.StatusNotFound)
return
} else if err != nil {
http.Error(w, fmt.Sprintf("internal error: %q", err), http.StatusInternalServerError)
return
}
if workdir == "" {
workdir = blobWorkDir
}

// warm-up, didn't need response content
if req.Method == http.MethodHead {
Expand Down Expand Up @@ -287,7 +290,7 @@ func isNoWebsocketRequest(req *http.Request, match *mux.RouteMatch) bool {

// Prepare downloads a blob and prepares it for use independently of any request
func (reg *Server) Prepare(ctx context.Context, ref string) (err error) {
_, _, err = reg.refstore.BlobFor(ctx, ref, false)
_, _, _, err = reg.refstore.BlobFor(ctx, ref, false)
return
}

Expand Down
91 changes: 76 additions & 15 deletions components/blobserve/pkg/blobserve/refstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package blobserve

import (
"context"
"encoding/json"
"net/http"
"sync"
"time"
Expand Down Expand Up @@ -89,7 +90,8 @@ func newRefStore(cfg blobserve_config.BlobServe, resolver ResolverProvider) (*re
}

type refstate struct {
Digest string
Digest string
WorkDir string

ch chan error
}
Expand Down Expand Up @@ -127,19 +129,19 @@ type BlobForOpts struct {
Modifier map[string]FileModifier
}

func (store *refstore) BlobFor(ctx context.Context, ref string, readOnly bool) (fs http.FileSystem, hash string, err error) {
func (store *refstore) BlobFor(ctx context.Context, ref string, readOnly bool) (fs http.FileSystem, hash string, workDir string, err error) {
store.mu.RLock()
rs, exists := store.refcache[ref]
store.mu.RUnlock()

if exists {
err = rs.Wait(ctx)
if err != nil {
return nil, "", err
return nil, "", "", err
}
}
if !exists && readOnly {
return nil, "", errdefs.ErrNotFound
return nil, "", "", errdefs.ErrNotFound
}

blobState := blobUnknown
Expand All @@ -152,7 +154,7 @@ func (store *refstore) BlobFor(ctx context.Context, ref string, readOnly bool) (
// if refcache thinks the blob should exist, but it doesn't, we force a redownload.
err = store.downloadBlobFor(ctx, ref, exists)
if err != nil {
return nil, "", err
return nil, "", "", err
}
store.mu.RLock()
rs = store.refcache[ref]
Expand All @@ -163,7 +165,7 @@ func (store *refstore) BlobFor(ctx context.Context, ref string, readOnly bool) (
// We have to wait for the download to actually finish.
err = rs.Wait(ctx)
if err != nil {
return nil, "", err
return nil, "", "", err
}

// now that we've (re-)attempted to download the blob, it must exist.
Expand All @@ -176,10 +178,10 @@ func (store *refstore) BlobFor(ctx context.Context, ref string, readOnly bool) (
WithField("digest", rs.Digest).
WithField("blobState", blobState).
Error("Blob could not be found for an unknown reason.")
return nil, "", errdefs.ErrUnknown
return nil, "", "", errdefs.ErrUnknown
}

return fs, rs.Digest, nil
return fs, rs.Digest, rs.WorkDir, nil
}

func (store *refstore) Close() {
Expand Down Expand Up @@ -244,12 +246,13 @@ func (store *refstore) handleRequest(ctx context.Context, ref string, force bool

resolver := store.Resolver()

layer, err := resolveRef(ctx, ref, resolver)
layer, workDir, err := resolveRef(ctx, ref, resolver)
if err != nil {
return err
}
digest := layer.Digest.Hex()
rs.Digest = digest
rs.WorkDir = workDir

fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
Expand Down Expand Up @@ -296,28 +299,86 @@ func (store *refstore) handleRequest(ctx context.Context, ref string, force bool
}
}

func resolveRef(ctx context.Context, ref string, resolver remotes.Resolver) (*ociv1.Descriptor, error) {
func resolveRef(ctx context.Context, ref string, resolver remotes.Resolver) (*ociv1.Descriptor, string, error) {
_, desc, err := resolver.Resolve(ctx, ref)
if err != nil {
return nil, err
return nil, "", err
}
fetcher, err := resolver.Fetcher(ctx, ref)
if err != nil {
return nil, err
return nil, "", err
}
manifest, _, err := registry.DownloadManifest(ctx, registry.AsFetcherFunc(fetcher), desc)
if err != nil {
return nil, err
return nil, "", err
}

layerCount := len(manifest.Layers)
if layerCount <= 0 {
log.WithField("ref", ref).Error("image has no layers - cannot serve its blob")
return nil, errdefs.ErrNotFound
return nil, "", errdefs.ErrNotFound
}
if layerCount > 1 {
log.WithField("ref", ref).Warn("image has more than one layers - serving from first layer only")
}
blobLayer := manifest.Layers[0]
return &blobLayer, nil

config, err := fetcher.Fetch(ctx, manifest.Config)
if err != nil {
return nil, "", err
}
defer config.Close()

var tmp ManifestJSON

err = json.NewDecoder(config).Decode(&tmp)
if err != nil {
return nil, "", xerrors.Errorf("cannot unmarshal image config: %w", err)
}

workDir := ""
if tmp.Config.Labels.Manifest != "" {
manifest := &IDEManifest{}
err = json.Unmarshal([]byte(tmp.Config.Labels.Manifest), manifest)
if err != nil {
return nil, "", xerrors.Errorf("cannot unmarshal IDE manifest: %w", err)
}
if manifest.Name != "" {
if manifest.Kind == "desktop" {
workDir = "/ide-desktop/" + manifest.Name
} else {
workDir = "/ide/" + manifest.Name
}
}
}

return &blobLayer, workDir, nil
}

// TODO get rid of duplication with ide-service via common package
type ManifestJSON struct {
Config struct {
Labels IDELabels `json:"Labels"`
} `json:"config"`
}

type IDELabels struct {
Manifest string `json:"io.gitpod.ide.manifest,omitempty"`
}

/*
{
"name": "xterm",
"kind": "browser",
"version": "1.0.0",
"title": "Terminal",
"icon": "terminal.svg"
}
*/
type IDEManifest struct {
Name string `json:"name,omitempty"`
Kind string `json:"kind,omitempty"`
Version string `json:"version,omitempty"`
Title string `json:"title,omitempty"`
Icon string `json:"icon,omitempty"`
}
Loading