mirror of
https://github.com/dokku/dokku.git
synced 2026-07-10 20:40:43 +02:00
Adds a `--format json` flag to `plugin:list` whose output includes each plugin's install source - for git-based third-party plugins, the git remote URL, the checked-out commit, and the followed branch - so the set of installed plugins can be reconstructed elsewhere. Closes #8798.
137 lines
3.6 KiB
Go
137 lines
3.6 KiB
Go
package plugin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
// PluginInfo describes an installed dokku plugin
|
|
type PluginInfo struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Enabled bool `json:"enabled"`
|
|
Core bool `json:"core"`
|
|
Description string `json:"description"`
|
|
SourceURL string `json:"source_url"`
|
|
Committish string `json:"committish"`
|
|
Branch string `json:"branch"`
|
|
}
|
|
|
|
// CommandList lists all installed plugins in the specified format
|
|
func CommandList(format string) error {
|
|
if format != "json" {
|
|
return fmt.Errorf("Invalid output format specified, supported formats: json")
|
|
}
|
|
|
|
plugins, err := listPlugins()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
out, err := json.Marshal(plugins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
common.Log(string(out))
|
|
return nil
|
|
}
|
|
|
|
// listPlugins returns metadata for every installed plugin, enriched with git
|
|
// source information for git-based third-party plugins
|
|
func listPlugins() ([]PluginInfo, error) {
|
|
result, err := common.CallExecCommand(common.ExecCommandInput{
|
|
Command: "plugn",
|
|
Args: []string{"list"},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
coreEnabledPath := common.MustGetEnv("PLUGIN_CORE_ENABLED_PATH")
|
|
availablePath := common.MustGetEnv("PLUGIN_AVAILABLE_PATH")
|
|
|
|
plugins := parsePlugnList(result.StdoutContents())
|
|
|
|
errs := new(errgroup.Group)
|
|
for i := range plugins {
|
|
i := i
|
|
errs.Go(func() error {
|
|
plugins[i].Core = common.IsSymlink(filepath.Join(coreEnabledPath, plugins[i].Name))
|
|
enrichGitSource(&plugins[i], availablePath)
|
|
return nil
|
|
})
|
|
}
|
|
if err := errs.Wait(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return plugins, nil
|
|
}
|
|
|
|
// parsePlugnList parses the output of `plugn list` into plugin metadata. Each
|
|
// data line is formatted as `<name> <version> <enabled|disabled> <description>`,
|
|
// preceded by a `plugn: <treeish>` header line that is skipped.
|
|
func parsePlugnList(output string) []PluginInfo {
|
|
plugins := []PluginInfo{}
|
|
for _, line := range strings.Split(output, "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 3 || fields[0] == "plugn:" {
|
|
continue
|
|
}
|
|
|
|
description := ""
|
|
if len(fields) > 3 {
|
|
description = strings.Join(fields[3:], " ")
|
|
}
|
|
|
|
plugins = append(plugins, PluginInfo{
|
|
Name: fields[0],
|
|
Version: fields[1],
|
|
Enabled: fields[2] == "enabled",
|
|
Description: description,
|
|
})
|
|
}
|
|
|
|
return plugins
|
|
}
|
|
|
|
// enrichGitSource populates the git source fields for a git-based plugin,
|
|
// leaving them empty for core, tarball, or file-based installs
|
|
func enrichGitSource(info *PluginInfo, availablePath string) {
|
|
pluginDir := filepath.Join(availablePath, info.Name)
|
|
if _, err := os.Stat(filepath.Join(pluginDir, ".git")); err != nil {
|
|
return
|
|
}
|
|
|
|
info.SourceURL = gitOutput(pluginDir, "remote", "get-url", "origin")
|
|
info.Committish = gitOutput(pluginDir, "rev-parse", "HEAD")
|
|
if branch := gitOutput(pluginDir, "rev-parse", "--abbrev-ref", "HEAD"); branch != "HEAD" {
|
|
info.Branch = branch
|
|
}
|
|
}
|
|
|
|
// gitOutput runs a git command in the given directory and returns its trimmed
|
|
// stdout, or an empty string on error. The directory is marked as safe so the
|
|
// lookup works regardless of which user invokes plugin:list.
|
|
func gitOutput(dir string, args ...string) string {
|
|
gitArgs := append([]string{"-c", "safe.directory=" + dir}, args...)
|
|
result, err := common.CallExecCommand(common.ExecCommandInput{
|
|
Command: "git",
|
|
Args: gitArgs,
|
|
WorkingDirectory: dir,
|
|
})
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return result.StdoutContents()
|
|
}
|