-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathinstall.go
More file actions
128 lines (112 loc) · 3.87 KB
/
install.go
File metadata and controls
128 lines (112 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package command
import (
"fmt"
"log"
"path"
"regexp"
"strings"
"github.com/privateerproj/privateer-sdk/internal/install"
"github.com/privateerproj/privateer-sdk/internal/registry"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var validNameSegment = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]*$`)
// GetInstallCmd returns the install command that can be added to a root command.
func GetInstallCmd(writer Writer) *cobra.Command {
installCmd := &cobra.Command{
Use: "install [plugin-name]",
Short: "Install a vetted plugin from the registry.",
Long: "Resolve the plugin name to registry metadata, then download the plugin binary from the release URL into the binaries path.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
pluginName := args[0]
return installPlugin(writer, pluginName)
},
}
return installCmd
}
func installPlugin(writer Writer, pluginName string) error {
client := registry.NewClient()
// Fetch the vetted list to validate the plugin name
vetted, err := client.GetVettedList()
if err != nil {
return fmt.Errorf("fetching vetted plugin list: %w", err)
}
if !isVetted(vetted.Plugins, pluginName) {
return fmt.Errorf("plugin %q is not in the vetted plugin list", pluginName)
}
// Parse owner/repo from plugin name
owner, repo, err := parsePluginName(pluginName)
if err != nil {
return err
}
// Fetch plugin metadata
_, _ = fmt.Fprintf(writer, "Fetching metadata for %s/%s...\n", owner, repo)
pluginData, err := client.GetPluginData(owner, repo)
if err != nil {
return fmt.Errorf("fetching plugin data: %w", err)
}
// Determine download URL
downloadURL, err := resolveDownloadURL(pluginData)
if err != nil {
return err
}
destDir := viper.GetString("binaries-path")
binaryName := path.Base(pluginData.Name)
_, _ = fmt.Fprintf(writer, "Downloading %s to %s...\n", binaryName, destDir)
err = install.FromURL(downloadURL, destDir, binaryName)
if err != nil {
return fmt.Errorf("installing plugin: %w", err)
}
_, _ = fmt.Fprintf(writer, "Successfully installed %s\n", pluginData.Name)
if err := writer.Flush(); err != nil {
log.Printf("Error flushing writer: %v", err)
}
return nil
}
// parsePluginName splits a plugin name into owner and repo.
// Accepts formats: "owner/repo" or just "repo" (assumes "privateerproj" as owner).
// Returns an error if the name is empty or contains path traversal characters.
func parsePluginName(name string) (owner, repo string, err error) {
name = strings.TrimSpace(name)
if name == "" {
return "", "", fmt.Errorf("plugin name must not be empty")
}
parts := strings.SplitN(name, "/", 2)
if len(parts) == 2 {
owner, repo = parts[0], parts[1]
} else {
owner, repo = "privateerproj", name
}
if !validNameSegment.MatchString(owner) {
return "", "", fmt.Errorf("invalid owner %q: must match %s", owner, validNameSegment.String())
}
if !validNameSegment.MatchString(repo) {
return "", "", fmt.Errorf("invalid repo %q: must match %s", repo, validNameSegment.String())
}
return owner, repo, nil
}
func isVetted(plugins []string, name string) bool {
name = strings.TrimSpace(name)
for _, p := range plugins {
if strings.TrimSpace(p) == name {
return true
}
}
return false
}
// resolveDownloadURL determines the download URL for a plugin.
// If the plugin has a direct download URL, use that.
// Otherwise, infer from GitHub releases using the source and latest version.
func resolveDownloadURL(data *registry.PluginData) (string, error) {
if data.Download != "" {
return data.Download, nil
}
if data.Source == "" || data.Latest == "" {
return "", fmt.Errorf("plugin %s has no download URL and no source/version to infer one from", data.Name)
}
base := install.InferGitHubReleaseBase(data.Source, data.Latest)
binaryName := path.Base(data.Name)
artifact := install.InferArtifactFilename(binaryName)
return fmt.Sprintf("%s/%s", base, artifact), nil
}