175 lines
6.2 KiB
Go
175 lines
6.2 KiB
Go
package pluginstore
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type Manifest struct {
|
|
SchemaVersion int `yaml:"schema-version,omitempty" json:"schema_version,omitempty"`
|
|
ID string `yaml:"id,omitempty" json:"id,omitempty"`
|
|
Name string `yaml:"name,omitempty" json:"name,omitempty"`
|
|
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
|
Author string `yaml:"author,omitempty" json:"author,omitempty"`
|
|
Version string `yaml:"version,omitempty" json:"version,omitempty"`
|
|
ReleaseTag string `yaml:"release-tag,omitempty" json:"release_tag,omitempty"`
|
|
Repository string `yaml:"repository,omitempty" json:"repository,omitempty"`
|
|
Logo string `yaml:"logo,omitempty" json:"logo,omitempty"`
|
|
Homepage string `yaml:"homepage,omitempty" json:"homepage,omitempty"`
|
|
License string `yaml:"license,omitempty" json:"license,omitempty"`
|
|
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`
|
|
SourceID string `yaml:"source-id,omitempty" json:"source_id,omitempty"`
|
|
SourceName string `yaml:"source-name,omitempty" json:"source_name,omitempty"`
|
|
SourceURL string `yaml:"source-url,omitempty" json:"source_url,omitempty"`
|
|
Install InstallPlan `yaml:"install,omitempty" json:"install,omitempty"`
|
|
}
|
|
|
|
func ManifestFromRelease(source Source, plugin Plugin, release Release) (Manifest, error) {
|
|
version, errVersion := ReleaseVersion(release)
|
|
if errVersion != nil {
|
|
return Manifest{}, errVersion
|
|
}
|
|
return manifestFromPlugin(source, plugin, Manifest{
|
|
Version: version,
|
|
ReleaseTag: strings.TrimSpace(release.TagName),
|
|
Repository: strings.TrimSpace(plugin.Repository),
|
|
Install: InstallPlan{Type: InstallTypeGitHubRelease},
|
|
}), nil
|
|
}
|
|
|
|
func ManifestFromPlugin(source Source, plugin Plugin) (Manifest, error) {
|
|
if errValidate := ValidatePlugin(plugin); errValidate != nil {
|
|
return Manifest{}, errValidate
|
|
}
|
|
switch PluginInstallType(plugin) {
|
|
case InstallTypeDirect:
|
|
return Manifest{
|
|
SchemaVersion: SchemaVersionV2,
|
|
ID: strings.TrimSpace(plugin.ID),
|
|
Version: strings.TrimSpace(plugin.Version),
|
|
SourceID: strings.TrimSpace(source.ID),
|
|
SourceName: strings.TrimSpace(source.Name),
|
|
SourceURL: strings.TrimSpace(source.URL),
|
|
Install: InstallPlan{Type: InstallTypeDirect},
|
|
}, nil
|
|
case InstallTypeGitHubRelease:
|
|
return Manifest{}, fmt.Errorf("github-release manifest requires a resolved release")
|
|
default:
|
|
return Manifest{}, fmt.Errorf("unsupported install type %q", plugin.Install.Type)
|
|
}
|
|
}
|
|
|
|
func manifestFromPlugin(source Source, plugin Plugin, base Manifest) Manifest {
|
|
base.ID = strings.TrimSpace(plugin.ID)
|
|
base.Name = strings.TrimSpace(plugin.Name)
|
|
base.Description = strings.TrimSpace(plugin.Description)
|
|
base.Author = strings.TrimSpace(plugin.Author)
|
|
base.Logo = strings.TrimSpace(plugin.Logo)
|
|
base.Homepage = strings.TrimSpace(plugin.Homepage)
|
|
base.License = strings.TrimSpace(plugin.License)
|
|
base.Tags = append([]string(nil), plugin.Tags...)
|
|
base.SourceID = strings.TrimSpace(source.ID)
|
|
base.SourceName = strings.TrimSpace(source.Name)
|
|
base.SourceURL = strings.TrimSpace(source.URL)
|
|
return base
|
|
}
|
|
|
|
func (m Manifest) Plugin() Plugin {
|
|
return Plugin{
|
|
ID: strings.TrimSpace(m.ID),
|
|
Name: strings.TrimSpace(m.Name),
|
|
Description: strings.TrimSpace(m.Description),
|
|
Author: strings.TrimSpace(m.Author),
|
|
Version: strings.TrimSpace(m.Version),
|
|
Repository: strings.TrimSpace(m.Repository),
|
|
Logo: strings.TrimSpace(m.Logo),
|
|
Homepage: strings.TrimSpace(m.Homepage),
|
|
License: strings.TrimSpace(m.License),
|
|
Tags: append([]string(nil), m.Tags...),
|
|
Install: NormalizeInstallPlan(m.Install),
|
|
}
|
|
}
|
|
|
|
func (m Manifest) InstallType() string {
|
|
installType := strings.ToLower(strings.TrimSpace(m.Install.Type))
|
|
if installType == "" {
|
|
return InstallTypeGitHubRelease
|
|
}
|
|
return installType
|
|
}
|
|
|
|
func (m Manifest) Validate() error {
|
|
version := strings.TrimSpace(m.Version)
|
|
if version == "" {
|
|
return fmt.Errorf("missing required field version")
|
|
}
|
|
if !validPluginVersion(normalizeVersion(version)) {
|
|
return fmt.Errorf("invalid plugin version %q", m.Version)
|
|
}
|
|
switch m.InstallType() {
|
|
case InstallTypeDirect:
|
|
if m.SchemaVersion != 0 && m.SchemaVersion != SchemaVersionV2 {
|
|
return fmt.Errorf("unsupported schema-version %d", m.SchemaVersion)
|
|
}
|
|
if errID := validateManifestPluginID(m.ID); errID != nil {
|
|
return errID
|
|
}
|
|
plan := NormalizeInstallPlan(m.Install)
|
|
plan.Type = InstallTypeDirect
|
|
if len(plan.Artifacts) > 0 {
|
|
return ValidateInstallPlan(plan)
|
|
}
|
|
return validateManifestSourceURL(m.SourceURL)
|
|
case InstallTypeGitHubRelease:
|
|
releaseTag := strings.TrimSpace(m.ReleaseTag)
|
|
if releaseTag == "" {
|
|
return fmt.Errorf("missing required field release-tag")
|
|
}
|
|
plugin := m.Plugin()
|
|
plugin.Install = InstallPlan{Type: InstallTypeGitHubRelease}
|
|
if errValidate := ValidatePlugin(plugin); errValidate != nil {
|
|
return errValidate
|
|
}
|
|
releaseVersion, errVersion := ReleaseVersion(Release{TagName: releaseTag})
|
|
if errVersion != nil {
|
|
return errVersion
|
|
}
|
|
if releaseVersion != normalizeVersion(version) {
|
|
return fmt.Errorf("release-tag %q resolves version %q, want %q", releaseTag, releaseVersion, normalizeVersion(version))
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unsupported install type %q", m.Install.Type)
|
|
}
|
|
}
|
|
|
|
func validateManifestPluginID(id string) error {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
return fmt.Errorf("missing required field id")
|
|
}
|
|
if !validPluginID(id) {
|
|
return fmt.Errorf("invalid plugin id %q", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateManifestSourceURL(sourceURL string) error {
|
|
sourceURL = strings.TrimSpace(sourceURL)
|
|
if sourceURL == "" {
|
|
return fmt.Errorf("missing required field source-url")
|
|
}
|
|
parsed, errParse := url.Parse(sourceURL)
|
|
if errParse != nil || parsed.Scheme == "" || parsed.Host == "" {
|
|
return fmt.Errorf("invalid source-url")
|
|
}
|
|
if parsed.Scheme != "https" && parsed.Scheme != "http" {
|
|
return fmt.Errorf("source-url must use http or https")
|
|
}
|
|
if hasSensitiveQueryParameter(parsed) {
|
|
return fmt.Errorf("source-url contains sensitive query parameter")
|
|
}
|
|
return nil
|
|
}
|