Skip to content

support jb plugins from image #18732

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
179 changes: 157 additions & 22 deletions components/ide/jetbrains/launcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
package main

import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"io/ioutil"
"net"
"net/http"
Expand Down Expand Up @@ -420,12 +423,16 @@ func launch(launchCtx *LaunchContext) {
launchCtx.projectConfigDir = fmt.Sprintf("%s/RemoteDev-%s/%s", launchCtx.configDir, launchCtx.info.ProductCode, strings.ReplaceAll(launchCtx.projectContextDir, "/", "_"))
launchCtx.env = resolveLaunchContextEnv(launchCtx.configDir, launchCtx.systemDir)

// sync initial options
err = syncOptions(launchCtx)
err = syncInitialContent(launchCtx, Options)
if err != nil {
log.WithError(err).Error("failed to sync initial options")
}

err = syncInitialContent(launchCtx, Plugins)
if err != nil {
log.WithError(err).Error("failed to sync initial plugins")
}

// install project plugins
version_2022_1, _ := version.NewVersion("2022.1")
if version_2022_1.LessThanOrEqual(launchCtx.backendVersion) {
Expand Down Expand Up @@ -710,53 +717,181 @@ func resolveProductInfo(backendDir string) (*ProductInfo, error) {
return &info, err
}

func syncOptions(launchCtx *LaunchContext) error {
userHomeDir, err := os.UserHomeDir()
type SyncTarget string

const (
Options SyncTarget = "options"
Plugins SyncTarget = "plugins"
)

func syncInitialContent(launchCtx *LaunchContext, target SyncTarget) error {
destDir, err, alreadySynced := ensureInitialSyncDest(launchCtx, target)
if alreadySynced {
log.Infof("initial %s is already synced, skipping", target)
return nil
}
if err != nil {
return err
}

srcDirs, err := collectSyncSources(launchCtx, target)
if err != nil {
return err
}
if len(srcDirs) == 0 {
// nothing to sync
return nil
}

for _, srcDir := range srcDirs {
if target == Plugins {
files, err := ioutil.ReadDir(srcDir)
if err != nil {
return err
}

for _, file := range files {
err := syncPlugin(file, srcDir, destDir)
if err != nil {
log.WithError(err).WithField("file", file.Name()).WithField("srcDir", srcDir).WithField("destDir", destDir).Error("failed to sync plugin")
}
}
} else {
cp := exec.Command("cp", "-rf", srcDir+"/.", destDir)
err = cp.Run()
if err != nil {
return err
}
}
}
return nil
}

func syncPlugin(file fs.FileInfo, srcDir, destDir string) error {
if file.IsDir() {
_, err := os.Stat(filepath.Join(destDir, file.Name()))
if !os.IsNotExist(err) {
log.WithField("plugin", file.Name()).Info("plugin is already synced, skipping")
return nil
}
return exec.Command("cp", "-rf", filepath.Join(srcDir, file.Name()), destDir).Run()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not-blocking): Use golang instead of exec cp command (most images should have cp so it's a nit-suggestion)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i think we discussed when we introduced options, and decided just to use cp

but just of curiosity what would use if you go with golang

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filepath.Walk or lib like github.com/otiai10/copy, but cp can be a good option too

}
if filepath.Ext(file.Name()) != ".zip" {
return nil
}
archiveFile := filepath.Join(srcDir, file.Name())
rootDir, err := getRootDirFromArchive(archiveFile)
if err != nil {
return err
}
_, err = os.Stat(filepath.Join(destDir, rootDir))
if !os.IsNotExist(err) {
log.WithField("plugin", rootDir).Info("plugin is already synced, skipping")
return nil
}
return unzipArchive(archiveFile, destDir)
}

func ensureInitialSyncDest(launchCtx *LaunchContext, target SyncTarget) (string, error, bool) {
targetDestDir := launchCtx.projectConfigDir
if target == Plugins {
targetDestDir = launchCtx.backendDir
}
destDir := fmt.Sprintf("%s/%s", targetDestDir, target)
if target == Options {
_, err := os.Stat(destDir)
if !os.IsNotExist(err) {
return "", nil, true
}
err = os.MkdirAll(destDir, os.ModePerm)
if err != nil {
return "", err, false
}
}
return destDir, nil, false
}

func collectSyncSources(launchCtx *LaunchContext, target SyncTarget) ([]string, error) {
userHomeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
var srcDirs []string
for _, srcDir := range []string{
fmt.Sprintf("%s/.gitpod/jetbrains/options", userHomeDir),
fmt.Sprintf("%s/.gitpod/jetbrains/%s/options", userHomeDir, launchCtx.alias),
fmt.Sprintf("%s/.gitpod/jetbrains/options", launchCtx.projectDir),
fmt.Sprintf("%s/.gitpod/jetbrains/%s/options", launchCtx.projectDir, launchCtx.alias),
fmt.Sprintf("%s/.gitpod/jetbrains/%s", userHomeDir, target),
fmt.Sprintf("%s/.gitpod/jetbrains/%s/%s", userHomeDir, launchCtx.alias, target),
fmt.Sprintf("%s/.gitpod/jetbrains/%s", launchCtx.projectDir, target),
fmt.Sprintf("%s/.gitpod/jetbrains/%s/%s", launchCtx.projectDir, launchCtx.alias, target),
} {
srcStat, err := os.Stat(srcDir)
if os.IsNotExist(err) {
// nothing to sync
continue
}
if err != nil {
return err
return nil, err
}
if !srcStat.IsDir() {
return fmt.Errorf("%s is not a directory", srcDir)
return nil, fmt.Errorf("%s is not a directory", srcDir)
}
srcDirs = append(srcDirs, srcDir)
}
if len(srcDirs) == 0 {
// nothing to sync
return nil
return srcDirs, nil
}

func getRootDirFromArchive(zipPath string) (string, error) {
r, err := zip.OpenReader(zipPath)
if err != nil {
return "", err
}
defer r.Close()

destDir := fmt.Sprintf("%s/options", launchCtx.projectConfigDir)
_, err = os.Stat(destDir)
if !os.IsNotExist(err) {
// already synced skipping, i.e. restart of jb backend
return nil
if len(r.File) == 0 {
return "", fmt.Errorf("empty archive")
}
err = os.MkdirAll(destDir, os.ModePerm)

// Assuming the first file in the zip is the root directory or a file in the root directory
return strings.SplitN(r.File[0].Name, "/", 2)[0], nil
}

func unzipArchive(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()

for _, srcDir := range srcDirs {
cp := exec.Command("cp", "-rf", srcDir+"/.", destDir)
err = cp.Run()
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()

fpath := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
err := os.MkdirAll(fpath, os.ModePerm)
if err != nil {
return err
}
} else {
fdir := filepath.Dir(fpath)
err := os.MkdirAll(fdir, os.ModePerm)
if err != nil {
return err
}

outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer outFile.Close()

_, err = io.Copy(outFile, rc)
if err != nil {
return err
}
}
}
return nil
}
Expand Down
24 changes: 24 additions & 0 deletions components/ide/jetbrains/launcher/rebuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Copyright (c) 2023 Gitpod GmbH. All rights reserved.
# Licensed under the GNU Affero General Public License (AGPL).
# See License.AGPL.txt in the project root for license information.

set -Eeuo pipefail

DIR="$(dirname "$(realpath "$0")")"
COMPONENT="$(basename "$DIR")"
cd "$DIR"

# build
go build .
echo "$COMPONENT built"

DIST_COMPONENT="jb-launcher"
mv "$COMPONENT" "$DIST_COMPONENT"
echo "rename $COMPONENT to $DIST_COMPONENT"
COMPONENT="$DIST_COMPONENT"

DEST="/ide-desktop"
sudo rm -rf "$DEST/$COMPONENT" && true
sudo mv ./"$COMPONENT" "$DEST"
echo "$COMPONENT in $DEST replaced"