Skip to content

Feature: Update to JSON Plugin Schema to Support Private Plugin Sources #2470

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

Open
wants to merge 55 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
f7e15b4
adding boilerplate for gitlab plugin
Bee-Mar May 25, 2024
5306195
basic functionality for public facing gitlab plugins working
Bee-Mar May 25, 2024
34359d8
consolidated gitlab and github logic
Bee-Mar May 25, 2024
73dc9af
still work in progress, but bitbucket and gitlab public urls are handled
Bee-Mar May 26, 2024
6c4c0f0
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Jun 17, 2024
844926f
slightly closer
Bee-Mar Jun 17, 2024
8973282
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Jun 30, 2024
8234390
basic retrieval of private plugins. need to clean it up
Bee-Mar Jul 1, 2024
e855a1c
removed bad debug stmt
Bee-Mar Jul 1, 2024
2ddf616
added port and subgroup arguments to handle more edge cases of privat…
Bee-Mar Jul 3, 2024
9c4bc59
changed to use explicit ssh protocol
Bee-Mar Jul 3, 2024
6448a7b
added check to see if the plugin file exists
Bee-Mar Jul 3, 2024
23bcc4c
devbox schema for plugin layout
Bee-Mar Jul 13, 2024
97ee2c7
update draft version
Bee-Mar Jul 13, 2024
2a81f8b
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Jul 13, 2024
1db728a
wip;
Bee-Mar Jul 14, 2024
bd2b547
reworking things to work with the flake.Ref struct
Bee-Mar Jul 15, 2024
e17c830
removing undo tree file
Bee-Mar Jul 15, 2024
de013d9
reworking things to use a json structure as input for plugins
Bee-Mar Jul 16, 2024
e1c50ac
placeholder
Bee-Mar Jul 19, 2024
ea64956
revisiting the json schema stuff
Bee-Mar Jul 24, 2024
ac42e18
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Jul 28, 2024
158880a
basic gitlab repo working
Bee-Mar Jul 28, 2024
23bd741
bitbucket function at basic level
Bee-Mar Jul 28, 2024
d07086f
fixed github url structure
Bee-Mar Jul 28, 2024
3372b09
substitute slashes for periods when using repo name in plugin.name to…
Bee-Mar Jul 28, 2024
b47c544
some cleanup
Bee-Mar Jul 28, 2024
e3d7e55
pull main; wip
Bee-Mar Oct 30, 2024
c9a7554
include all plugin caches in `devbox update`
Bee-Mar Oct 30, 2024
665eca9
corrected arg provided to slog error func
Bee-Mar Oct 30, 2024
d16d1af
update port to be int in ref struct (silly mistake)
Bee-Mar Nov 1, 2024
c93d661
private plugins with hashing check done
Bee-Mar Nov 1, 2024
a721ad4
update example devbox.json files for unit tests
Bee-Mar Nov 1, 2024
352be83
some cleanup
Bee-Mar Nov 1, 2024
ab5053b
cleaning up some tests
Bee-Mar Nov 10, 2024
0839434
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Nov 10, 2024
01220b1
basic tests for gitlab and bitbucket
Bee-Mar Nov 10, 2024
089acf9
added tests for ssh archives
Bee-Mar Nov 10, 2024
f7a3207
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Nov 24, 2024
174ba55
moved call to fetch plugin inside lambda
Bee-Mar Nov 24, 2024
2d9f7a1
removed overriding of GitHub flake type
Bee-Mar Nov 24, 2024
ce63909
added comment
Bee-Mar Nov 24, 2024
e856460
wip
Bee-Mar Dec 22, 2024
2994677
pulled upstream
Bee-Mar Dec 22, 2024
b05fc25
truly...no idea why i went down that rabbit hole in the first place
Bee-Mar Dec 22, 2024
c93d43e
Update update.go
Bee-Mar Dec 23, 2024
fce2ea9
removed old updateme comments
Bee-Mar Dec 24, 2024
8b613b4
Merge branch 'feature/json-plugin-schema' of github.com:bee-mar/devbo…
Bee-Mar Dec 24, 2024
1cdda81
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Jan 11, 2025
d4ab61b
update version
Bee-Mar Jan 11, 2025
18d23de
updated include section examples
Bee-Mar Feb 2, 2025
ce5e13f
pull upstream
Bee-Mar Feb 2, 2025
3237787
Merge branch 'main' of https://github.com/jetify-com/devbox into feat…
Bee-Mar Feb 14, 2025
eee6c03
update devbox.lock
Bee-Mar Mar 14, 2025
8ddfc67
pulled upstream
Bee-Mar Mar 15, 2025
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
Prev Previous commit
Next Next commit
some cleanup
  • Loading branch information
Bee-Mar committed Nov 1, 2024
commit 352be83b3142c219177c2b15d0ffdf81c1d1532d
195 changes: 117 additions & 78 deletions internal/plugin/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
)

var sshCache = filecache.New[[]byte]("devbox/plugin/ssh")
var gitCache = filecache.New[[]byte]("devbox/plugin/git")
var githubCache = filecache.New[[]byte]("devbox/plugin/github")
var gitlabCache = filecache.New[[]byte]("devbox/plugin/gitlab")
var bitbucketCache = filecache.New[[]byte]("devbox/plugin/bitbucket")
Expand Down Expand Up @@ -78,115 +79,116 @@ func (p *gitPlugin) Hash() string {
return cachehash.Bytes([]byte(p.ref.String()))
}

func (p *gitPlugin) FileContent(subpath string) ([]byte, error) {
pluginLocation, err := p.url(subpath)
func (p *gitPlugin) fetchSSHArchive(location string) ([]byte, error) {
archiveDir, _ := os.MkdirTemp("", p.ref.Repo)
archive := filepath.Join(archiveDir, p.ref.Owner+".tar.gz")
args := strings.Fields(location + archive) // this is really just the base git archive command + file

defer os.RemoveAll(archiveDir)

cmd := exec.Command(args[0], args[1:]...)
_, err := cmd.Output()

if err != nil {
slog.Error("Error executing git archive: " + err.Error())
return nil, err
}

retrieveArchive := func() ([]byte, time.Duration, error) {
archiveDir, _ := os.MkdirTemp("", p.ref.Repo)
archive := filepath.Join(archiveDir, p.ref.Owner+".tar.gz")
args := strings.Fields(pluginLocation + archive) // this is really just the base git archive command + file

defer func() {
slog.Debug("Cleaning up retrieved files related to privately hosted plugin")
slog.Debug("Removing archive " + archive)
os.RemoveAll(archive)
slog.Debug("Removing archive directory " + archiveDir)
os.RemoveAll(archiveDir)
}()
reader, err := os.Open(archive)
err = fileutil.Untar(reader, archiveDir)

cmd := exec.Command(args[0], args[1:]...)
_, err := cmd.Output()

if err != nil {
slog.Error("Error executing git archive: " + err.Error())
return nil, 0, err
}
if err != nil {
slog.Error("Encountered error while trying to extract " + archive + ": " + err.Error())
return nil, err
}

reader, err := os.Open(archive)
err = fileutil.Untar(reader, archiveDir)
pluginJson := filepath.Join(archiveDir, p.ref.Dir, "plugin.json")
file, err := os.Open(pluginJson)

if err != nil {
slog.Error("Encountered error while trying to extract " + archive + ": " + err.Error())
return nil, 0, err
}
defer file.Close()
info, err := file.Stat()

pluginJson := filepath.Join(archiveDir, p.ref.Dir, "plugin.json")
file, err := os.Open(pluginJson)
defer file.Close()
if err != nil {
slog.Error("Error extracting file " + file.Name() + ". Cannot process plugin.")
return nil, err
}

info, err := file.Stat()
if info.Size() == 0 {
slog.Error("Extracted file " + file.Name() + " is empty. Cannot process plugin.")
return nil, err
}

if err != nil {
slog.Error("Error extracting file " + file.Name() + ". Cannot process plugin.")
return nil, 0, err
}
return io.ReadAll(file)
}

if info.Size() == 0 {
slog.Error("Extracted file " + file.Name() + " is empty. Cannot process plugin.")
return nil, 0, err
}
func (p *gitPlugin) fetchHttp(location string) ([]byte, error) {
req, err := p.request(location)

body, err := io.ReadAll(file)
if err != nil {
return nil, err
}

if err != nil {
return nil, 0, err
}
client := &http.Client{}
res, err := client.Do(req)

return body, 24 * time.Hour, nil
if err != nil {
return nil, err
}

retrieveHttp := func() ([]byte, time.Duration, error) {
req, err := p.request(pluginLocation)
defer res.Body.Close()

if err != nil {
return nil, 0, err
}
if res.StatusCode != http.StatusOK {
return nil, usererr.New(
"failed to get plugin %s @ %s (Status code %d). \nPlease make "+
"sure a plugin.json file exists in plugin directory.",
p.LockfileKey(),
req.URL.String(),
res.StatusCode,
)
}

client := &http.Client{}
res, err := client.Do(req)
return io.ReadAll(res.Body)
}

if err != nil {
return nil, 0, err
}
func (p *gitPlugin) FileContent(subpath string) ([]byte, error) {
location, err := p.url(subpath)

defer res.Body.Close()
if err != nil {
return nil, err
}

if res.StatusCode != http.StatusOK {
return nil, 0, usererr.New(
"failed to get plugin %s @ %s (Status code %d). \nPlease make "+
"sure a plugin.json file exists in plugin directory.",
p.LockfileKey(),
req.URL.String(),
res.StatusCode,
)
}
var bytes []byte

body, err := io.ReadAll(res.Body)
if p.ref.Type == flake.TypeSSH {
bytes, err = p.fetchSSHArchive(location)
} else {
bytes, err = p.fetchHttp(location)
}

if err != nil {
return nil, 0, err
}
if err != nil {
return nil, err
}

process := func() ([]byte, time.Duration, error) {
// Cache for 24 hours. Once we store the plugin in the lockfile, we
// should cache this indefinitely and only invalidate if the plugin
// is updated.
return body, 24 * time.Hour, nil
return bytes, 24 * time.Hour, nil
}

switch p.ref.Type {
case flake.TypeSSH:
return sshCache.GetOrSet(pluginLocation, retrieveArchive)
return sshCache.GetOrSet(location, process)
case flake.TypeGitHub:
return githubCache.GetOrSet(pluginLocation, retrieveHttp)
return githubCache.GetOrSet(location, process)
case flake.TypeGitLab:
return gitlabCache.GetOrSet(pluginLocation, retrieveHttp)
return gitlabCache.GetOrSet(location, process)
case flake.TypeBitBucket:
return bitbucketCache.GetOrSet(pluginLocation, retrieveHttp)
return bitbucketCache.GetOrSet(location, process)
case flake.TypeGit:
return gitCache.GetOrSet(location, process)
default:
slog.Error("Unable to handle flake ref type: " + p.ref.Type)
return nil, err
}
}
Expand All @@ -195,11 +197,7 @@ func (p *gitPlugin) url(subpath string) (string, error) {
switch p.ref.Type {
case flake.TypeSSH:
return p.sshBaseGitCommand()
case flake.TypeBitBucket:
fallthrough
case flake.TypeGitHub:
fallthrough
case flake.TypeGitLab:
case flake.TypeGit, flake.TypeGitHub, flake.TypeGitLab, flake.TypeBitBucket:
return p.repoUrl(subpath)
default:
return "", errors.New("Unsupported plugin type: " + p.ref.Type)
Expand Down Expand Up @@ -260,13 +258,54 @@ func (p *gitPlugin) bitbucketUrl(subpath string) (string, error) {
)
}

func (p *gitPlugin) genericGitUrl(subpath string) (string, error) {
address, err := url.JoinPath(
p.ref.Host,
p.ref.Repo,
cmp.Or(p.ref.Rev, p.ref.Ref, "main"),
p.ref.Dir,
subpath,
)

if err != nil {
return "", err
}

parsed, err := url.Parse(address)

if err != nil {
return "", err
}

// gitlab doesn't redirect master -> main or main -> master, so using "main"
// as the default in this case
query := parsed.Query()
query.Add("ref", cmp.Or(p.ref.Rev, p.ref.Ref, "main"))

if p.ref.Dir != "" {
query.Add("dir", p.ref.Dir)
}

if p.ref.Port != 0 {
query.Add("port", fmt.Sprintf("%d", p.ref.Port))
}

parsed.RawQuery = query.Encode()
query.Add("ref", cmp.Or(p.ref.Rev, p.ref.Ref, "main"))
parsed.RawQuery = query.Encode()

return parsed.String(), nil
}

func (p *gitPlugin) repoUrl(subpath string) (string, error) {
if p.ref.Type == flake.TypeGitHub {
return p.githubUrl(subpath)
} else if p.ref.Type == flake.TypeGitLab {
return p.gitlabUrl(subpath)
} else if p.ref.Type == flake.TypeBitBucket {
return p.bitbucketUrl(subpath)
} else if p.ref.Type == flake.TypeGit {
return p.genericGitUrl(subpath)
}

return "", errors.New("Unknown hostname provided in plugin: " + p.ref.Host)
Expand Down
2 changes: 1 addition & 1 deletion internal/plugin/includable.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func parseIncludable(ref flake.Ref, workingDir string) (Includable, error) {
switch ref.Type {
case flake.TypePath:
return newLocalPlugin(ref, workingDir)
case flake.TypeSSH, flake.TypeBuiltin, flake.TypeGitHub, flake.TypeGitLab, flake.TypeBitBucket:
case flake.TypeSSH, flake.TypeBuiltin, flake.TypeGitHub, flake.TypeGitLab, flake.TypeBitBucket, flake.TypeGit:
if ref.Host == "" {
ref.Host = ref.Type + ".com"
}
Expand Down
4 changes: 4 additions & 0 deletions nix/flake/flakeref.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
TypeSSH = "ssh"
TypeGitHub = "github"
TypeGitLab = "gitlab"
TypeGit = "git"
TypeBitBucket = "bitbucket"
TypeTarball = "tarball"
TypeBuiltin = "builtin"
Expand Down Expand Up @@ -203,6 +204,8 @@ func parseURLRef(ref string) (parsed Ref, fragment string, err error) {
parsed.Type = TypeSSH
} else if strings.HasPrefix(refURL.Scheme, TypeFile) {
parsed.Type = TypeFile
} else {
parsed.Type = TypeGit
}

parsed.URL = refURL.String()
Expand Down Expand Up @@ -256,6 +259,7 @@ func parseGitRef(refURL *url.URL, parsed *Ref) error {

parsed.Host = refURL.Query().Get("host")
parsed.Dir = refURL.Query().Get("dir")

if qRef := refURL.Query().Get("ref"); qRef != "" {
if parsed.Rev != "" {
return redact.Errorf("github flake reference has a ref and a rev")
Expand Down