Make plugin types public
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
@@ -24,122 +24,16 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// defaultCatalogBase points at the latest GitHub release of fluxcd/plugins.
|
// defaultCatalogBase points at the latest GitHub release of fluxcd/plugins.
|
||||||
defaultCatalogBase = "https://github.com/fluxcd/plugins/releases/latest/download/"
|
defaultCatalogBase = "https://github.com/fluxcd/plugins/releases/latest/download/"
|
||||||
envCatalogBase = "FLUXCD_PLUGIN_CATALOG"
|
envCatalogBase = "FLUXCD_PLUGIN_CATALOG"
|
||||||
|
|
||||||
pluginAPIVersion = "cli.fluxcd.io/v1beta1"
|
|
||||||
pluginKind = "Plugin"
|
|
||||||
catalogKind = "PluginCatalog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PluginManifest represents a single plugin's manifest from the catalog.
|
|
||||||
type PluginManifest struct {
|
|
||||||
// APIVersion is the manifest schema version (e.g. "cli.fluxcd.io/v1beta1").
|
|
||||||
APIVersion string `json:"apiVersion"`
|
|
||||||
|
|
||||||
// Kind is the manifest type, must be "Plugin".
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
|
|
||||||
// Name is the plugin name used in "flux <name>" invocations.
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Description is a short human-readable summary of the plugin.
|
|
||||||
Description string `json:"description"`
|
|
||||||
|
|
||||||
// Homepage is the URL to the plugin's documentation site.
|
|
||||||
Homepage string `json:"homepage,omitempty"`
|
|
||||||
|
|
||||||
// Source is the URL to the plugin's source repository.
|
|
||||||
Source string `json:"source,omitempty"`
|
|
||||||
|
|
||||||
// Bin is the binary name inside archives and the installed filename
|
|
||||||
// (e.g. "flux-operator"). On Windows ".exe" is appended automatically.
|
|
||||||
Bin string `json:"bin"`
|
|
||||||
|
|
||||||
// Versions lists available versions, newest first.
|
|
||||||
Versions []PluginVersion `json:"versions"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PluginVersion represents a version entry in a plugin manifest.
|
|
||||||
type PluginVersion struct {
|
|
||||||
// Version is the semantic version string (e.g. "0.45.0").
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// Platforms lists the platform-specific binaries for this version.
|
|
||||||
Platforms []PluginPlatform `json:"platforms"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PluginPlatform represents a platform-specific binary entry.
|
|
||||||
type PluginPlatform struct {
|
|
||||||
// OS is the target operating system (e.g. "darwin", "linux", "windows").
|
|
||||||
OS string `json:"os"`
|
|
||||||
|
|
||||||
// Arch is the target architecture (e.g. "amd64", "arm64").
|
|
||||||
Arch string `json:"arch"`
|
|
||||||
|
|
||||||
// URL is the download URL for the archive or binary.
|
|
||||||
URL string `json:"url"`
|
|
||||||
|
|
||||||
// Checksum is the expected digest in "<algorithm>:<hex>" format
|
|
||||||
// (e.g. "sha256:cd85d5d84d264...").
|
|
||||||
Checksum string `json:"checksum"`
|
|
||||||
|
|
||||||
// ExtractPath overrides the default binary lookup name inside archives.
|
|
||||||
// When set, it is matched as an exact path within the archive (e.g.
|
|
||||||
// "bin/flux-operator"). When empty, the archive is searched by the
|
|
||||||
// base name derived from the manifest's Bin field.
|
|
||||||
ExtractPath string `json:"extractPath,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PluginCatalog represents the generated catalog.yaml file.
|
|
||||||
type PluginCatalog struct {
|
|
||||||
// APIVersion is the catalog schema version (e.g. "cli.fluxcd.io/v1beta1").
|
|
||||||
APIVersion string `json:"apiVersion"`
|
|
||||||
|
|
||||||
// Kind is the catalog type, must be "PluginCatalog".
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
|
|
||||||
// Plugins lists all available plugins in the catalog.
|
|
||||||
Plugins []CatalogEntry `json:"plugins"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CatalogEntry is a single entry in the plugin catalog.
|
|
||||||
type CatalogEntry struct {
|
|
||||||
// Name is the plugin name.
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Description is a short human-readable summary of the plugin.
|
|
||||||
Description string `json:"description"`
|
|
||||||
|
|
||||||
// Homepage is the URL to the plugin's documentation site.
|
|
||||||
Homepage string `json:"homepage,omitempty"`
|
|
||||||
|
|
||||||
// Source is the URL to the plugin's source repository.
|
|
||||||
Source string `json:"source,omitempty"`
|
|
||||||
|
|
||||||
// License is the SPDX license identifier (e.g. "Apache-2.0").
|
|
||||||
License string `json:"license,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receipt records what was installed for a plugin.
|
|
||||||
type Receipt struct {
|
|
||||||
// Name is the plugin name (e.g. "operator").
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Version is the installed semantic version.
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// InstalledAt is the RFC 3339 timestamp of the installation.
|
|
||||||
InstalledAt string `json:"installedAt"`
|
|
||||||
|
|
||||||
// Platform records the platform-specific details used for installation.
|
|
||||||
Platform PluginPlatform `json:"platform"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CatalogClient fetches plugin manifests and catalogs from a remote URL.
|
// CatalogClient fetches plugin manifests and catalogs from a remote URL.
|
||||||
type CatalogClient struct {
|
type CatalogClient struct {
|
||||||
// BaseURL is the catalog base URL for fetching manifests.
|
// BaseURL is the catalog base URL for fetching manifests.
|
||||||
@@ -170,46 +64,46 @@ func (c *CatalogClient) baseURL() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchManifest fetches a single plugin manifest from the catalog.
|
// FetchManifest fetches a single plugin manifest from the catalog.
|
||||||
func (c *CatalogClient) FetchManifest(name string) (*PluginManifest, error) {
|
func (c *CatalogClient) FetchManifest(name string) (*plugintypes.Manifest, error) {
|
||||||
url := c.baseURL() + name + ".yaml"
|
url := c.baseURL() + name + ".yaml"
|
||||||
body, err := c.fetch(url)
|
body, err := c.fetch(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("plugin %q not found in catalog", name)
|
return nil, fmt.Errorf("plugin %q not found in catalog", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifest PluginManifest
|
var manifest plugintypes.Manifest
|
||||||
if err := yaml.Unmarshal(body, &manifest); err != nil {
|
if err := yaml.Unmarshal(body, &manifest); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse plugin manifest for %q: %w", name, err)
|
return nil, fmt.Errorf("failed to parse plugin manifest for %q: %w", name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if manifest.APIVersion != pluginAPIVersion {
|
if manifest.APIVersion != plugintypes.APIVersion {
|
||||||
return nil, fmt.Errorf("plugin %q has unsupported apiVersion %q (expected %q)", name, manifest.APIVersion, pluginAPIVersion)
|
return nil, fmt.Errorf("plugin %q has unsupported apiVersion %q (expected %q)", name, manifest.APIVersion, plugintypes.APIVersion)
|
||||||
}
|
}
|
||||||
if manifest.Kind != pluginKind {
|
if manifest.Kind != plugintypes.PluginKind {
|
||||||
return nil, fmt.Errorf("plugin %q has unexpected kind %q (expected %q)", name, manifest.Kind, pluginKind)
|
return nil, fmt.Errorf("plugin %q has unexpected kind %q (expected %q)", name, manifest.Kind, plugintypes.PluginKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &manifest, nil
|
return &manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchCatalog fetches the generated catalog.yaml.
|
// FetchCatalog fetches the generated catalog.yaml.
|
||||||
func (c *CatalogClient) FetchCatalog() (*PluginCatalog, error) {
|
func (c *CatalogClient) FetchCatalog() (*plugintypes.Catalog, error) {
|
||||||
url := c.baseURL() + "catalog.yaml"
|
url := c.baseURL() + "catalog.yaml"
|
||||||
body, err := c.fetch(url)
|
body, err := c.fetch(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch plugin catalog: %w", err)
|
return nil, fmt.Errorf("failed to fetch plugin catalog: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var catalog PluginCatalog
|
var catalog plugintypes.Catalog
|
||||||
if err := yaml.Unmarshal(body, &catalog); err != nil {
|
if err := yaml.Unmarshal(body, &catalog); err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse plugin catalog: %w", err)
|
return nil, fmt.Errorf("failed to parse plugin catalog: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if catalog.APIVersion != pluginAPIVersion {
|
if catalog.APIVersion != plugintypes.APIVersion {
|
||||||
return nil, fmt.Errorf("plugin catalog has unsupported apiVersion %q (expected %q)", catalog.APIVersion, pluginAPIVersion)
|
return nil, fmt.Errorf("plugin catalog has unsupported apiVersion %q (expected %q)", catalog.APIVersion, plugintypes.APIVersion)
|
||||||
}
|
}
|
||||||
if catalog.Kind != catalogKind {
|
if catalog.Kind != plugintypes.CatalogKind {
|
||||||
return nil, fmt.Errorf("plugin catalog has unexpected kind %q (expected %q)", catalog.Kind, catalogKind)
|
return nil, fmt.Errorf("plugin catalog has unexpected kind %q (expected %q)", catalog.Kind, plugintypes.CatalogKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &catalog, nil
|
return &catalog, nil
|
||||||
@@ -243,7 +137,7 @@ func newHTTPClient(timeout time.Duration) *http.Client {
|
|||||||
|
|
||||||
// ResolveVersion finds the requested version in the manifest.
|
// ResolveVersion finds the requested version in the manifest.
|
||||||
// If version is empty, returns the first (latest) version.
|
// If version is empty, returns the first (latest) version.
|
||||||
func ResolveVersion(manifest *PluginManifest, version string) (*PluginVersion, error) {
|
func ResolveVersion(manifest *plugintypes.Manifest, version string) (*plugintypes.Version, error) {
|
||||||
if len(manifest.Versions) == 0 {
|
if len(manifest.Versions) == 0 {
|
||||||
return nil, fmt.Errorf("plugin %q has no versions", manifest.Name)
|
return nil, fmt.Errorf("plugin %q has no versions", manifest.Name)
|
||||||
}
|
}
|
||||||
@@ -262,7 +156,7 @@ func ResolveVersion(manifest *PluginManifest, version string) (*PluginVersion, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResolvePlatform finds the platform entry matching the given OS and arch.
|
// ResolvePlatform finds the platform entry matching the given OS and arch.
|
||||||
func ResolvePlatform(pv *PluginVersion, goos, goarch string) (*PluginPlatform, error) {
|
func ResolvePlatform(pv *plugintypes.Version, goos, goarch string) (*plugintypes.Platform, error) {
|
||||||
for i := range pv.Platforms {
|
for i := range pv.Platforms {
|
||||||
if pv.Platforms[i].OS == goos && pv.Platforms[i].Arch == goarch {
|
if pv.Platforms[i].OS == goos && pv.Platforms[i].Arch == goarch {
|
||||||
return &pv.Platforms[i], nil
|
return &pv.Platforms[i], nil
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFetchManifest(t *testing.T) {
|
func TestFetchManifest(t *testing.T) {
|
||||||
@@ -168,9 +170,9 @@ plugins: []
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveVersion(t *testing.T) {
|
func TestResolveVersion(t *testing.T) {
|
||||||
manifest := &PluginManifest{
|
manifest := &plugintypes.Manifest{
|
||||||
Name: "operator",
|
Name: "operator",
|
||||||
Versions: []PluginVersion{
|
Versions: []plugintypes.Version{
|
||||||
{Version: "0.45.0"},
|
{Version: "0.45.0"},
|
||||||
{Version: "0.44.0"},
|
{Version: "0.44.0"},
|
||||||
},
|
},
|
||||||
@@ -204,7 +206,7 @@ func TestResolveVersion(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no versions", func(t *testing.T) {
|
t.Run("no versions", func(t *testing.T) {
|
||||||
_, err := ResolveVersion(&PluginManifest{Name: "empty"}, "")
|
_, err := ResolveVersion(&plugintypes.Manifest{Name: "empty"}, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error, got nil")
|
t.Fatal("expected error, got nil")
|
||||||
}
|
}
|
||||||
@@ -212,9 +214,9 @@ func TestResolveVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestResolvePlatform(t *testing.T) {
|
func TestResolvePlatform(t *testing.T) {
|
||||||
pv := &PluginVersion{
|
pv := &plugintypes.Version{
|
||||||
Version: "0.45.0",
|
Version: "0.45.0",
|
||||||
Platforms: []PluginPlatform{
|
Platforms: []plugintypes.Platform{
|
||||||
{OS: "darwin", Arch: "arm64", URL: "https://example.com/darwin_arm64.tar.gz"},
|
{OS: "darwin", Arch: "arm64", URL: "https://example.com/darwin_arm64.tar.gz"},
|
||||||
{OS: "linux", Arch: "amd64", URL: "https://example.com/linux_amd64.tar.gz"},
|
{OS: "linux", Arch: "amd64", URL: "https://example.com/linux_amd64.tar.gz"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Installer handles downloading, verifying, and installing plugins.
|
// Installer handles downloading, verifying, and installing plugins.
|
||||||
@@ -48,7 +50,7 @@ func NewInstaller() *Installer {
|
|||||||
|
|
||||||
// Install downloads, verifies, extracts, and installs a plugin binary
|
// Install downloads, verifies, extracts, and installs a plugin binary
|
||||||
// to the given plugin directory.
|
// to the given plugin directory.
|
||||||
func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *PluginVersion, plat *PluginPlatform) error {
|
func (inst *Installer) Install(pluginDir string, manifest *plugintypes.Manifest, pv *plugintypes.Version, plat *plugintypes.Platform) error {
|
||||||
tmpFile, err := os.CreateTemp("", "flux-plugin-*")
|
tmpFile, err := os.CreateTemp("", "flux-plugin-*")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create temp file: %w", err)
|
return fmt.Errorf("failed to create temp file: %w", err)
|
||||||
@@ -118,7 +120,7 @@ func (inst *Installer) Install(pluginDir string, manifest *PluginManifest, pv *P
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
receipt := Receipt{
|
receipt := plugintypes.Receipt{
|
||||||
Name: manifest.Name,
|
Name: manifest.Name,
|
||||||
Version: pv.Version,
|
Version: pv.Version,
|
||||||
InstalledAt: time.Now().UTC().Format(time.RFC3339),
|
InstalledAt: time.Now().UTC().Format(time.RFC3339),
|
||||||
@@ -156,13 +158,13 @@ func Uninstall(pluginDir, name string) error {
|
|||||||
|
|
||||||
// ReadReceipt reads the install receipt for a plugin.
|
// ReadReceipt reads the install receipt for a plugin.
|
||||||
// Returns nil if no receipt exists.
|
// Returns nil if no receipt exists.
|
||||||
func ReadReceipt(pluginDir, name string) *Receipt {
|
func ReadReceipt(pluginDir, name string) *plugintypes.Receipt {
|
||||||
data, err := os.ReadFile(receiptPath(pluginDir, name))
|
data, err := os.ReadFile(receiptPath(pluginDir, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var receipt Receipt
|
var receipt plugintypes.Receipt
|
||||||
if err := yaml.Unmarshal(data, &receipt); err != nil {
|
if err := yaml.Unmarshal(data, &receipt); err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -174,7 +176,7 @@ func receiptPath(pluginDir, name string) string {
|
|||||||
return filepath.Join(pluginDir, pluginPrefix+name+".yaml")
|
return filepath.Join(pluginDir, pluginPrefix+name+".yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeReceipt(pluginDir, name string, receipt *Receipt) error {
|
func writeReceipt(pluginDir, name string, receipt *plugintypes.Receipt) error {
|
||||||
data, err := yaml.Marshal(receipt)
|
data, err := yaml.Marshal(receipt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal receipt: %w", err)
|
return fmt.Errorf("failed to marshal receipt: %w", err)
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createTestTarGz creates a tar.gz archive containing a single file.
|
// createTestTarGz creates a tar.gz archive containing a single file.
|
||||||
@@ -164,12 +166,12 @@ func TestInstall(t *testing.T) {
|
|||||||
|
|
||||||
pluginDir := t.TempDir()
|
pluginDir := t.TempDir()
|
||||||
|
|
||||||
manifest := &PluginManifest{
|
manifest := &plugintypes.Manifest{
|
||||||
Name: "operator",
|
Name: "operator",
|
||||||
Bin: "flux-operator",
|
Bin: "flux-operator",
|
||||||
}
|
}
|
||||||
pv := &PluginVersion{Version: "0.45.0"}
|
pv := &plugintypes.Version{Version: "0.45.0"}
|
||||||
plat := &PluginPlatform{
|
plat := &plugintypes.Platform{
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
Arch: "amd64",
|
Arch: "amd64",
|
||||||
URL: server.URL + "/flux-operator_0.45.0_linux_amd64.tar.gz",
|
URL: server.URL + "/flux-operator_0.45.0_linux_amd64.tar.gz",
|
||||||
@@ -218,9 +220,9 @@ func TestInstallChecksumMismatch(t *testing.T) {
|
|||||||
|
|
||||||
pluginDir := t.TempDir()
|
pluginDir := t.TempDir()
|
||||||
|
|
||||||
manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"}
|
manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"}
|
||||||
pv := &PluginVersion{Version: "0.45.0"}
|
pv := &plugintypes.Version{Version: "0.45.0"}
|
||||||
plat := &PluginPlatform{
|
plat := &plugintypes.Platform{
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
Arch: "amd64",
|
Arch: "amd64",
|
||||||
URL: server.URL + "/archive.tar.gz",
|
URL: server.URL + "/archive.tar.gz",
|
||||||
@@ -253,9 +255,9 @@ func TestInstallBinaryNotInArchive(t *testing.T) {
|
|||||||
|
|
||||||
pluginDir := t.TempDir()
|
pluginDir := t.TempDir()
|
||||||
|
|
||||||
manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"}
|
manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"}
|
||||||
pv := &PluginVersion{Version: "0.45.0"}
|
pv := &plugintypes.Version{Version: "0.45.0"}
|
||||||
plat := &PluginPlatform{
|
plat := &plugintypes.Platform{
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
Arch: "amd64",
|
Arch: "amd64",
|
||||||
URL: server.URL + "/archive.tar.gz",
|
URL: server.URL + "/archive.tar.gz",
|
||||||
@@ -396,12 +398,12 @@ func TestInstallRawBinary(t *testing.T) {
|
|||||||
|
|
||||||
pluginDir := t.TempDir()
|
pluginDir := t.TempDir()
|
||||||
|
|
||||||
manifest := &PluginManifest{
|
manifest := &plugintypes.Manifest{
|
||||||
Name: "validate",
|
Name: "validate",
|
||||||
Bin: "flux-validate",
|
Bin: "flux-validate",
|
||||||
}
|
}
|
||||||
pv := &PluginVersion{Version: "1.2.3"}
|
pv := &plugintypes.Version{Version: "1.2.3"}
|
||||||
plat := &PluginPlatform{
|
plat := &plugintypes.Platform{
|
||||||
OS: runtime.GOOS,
|
OS: runtime.GOOS,
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
// URL filename deliberately differs from manifest.Bin — mimics a
|
// URL filename deliberately differs from manifest.Bin — mimics a
|
||||||
@@ -701,9 +703,9 @@ func TestInstallExtractPath(t *testing.T) {
|
|||||||
|
|
||||||
pluginDir := t.TempDir()
|
pluginDir := t.TempDir()
|
||||||
|
|
||||||
manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"}
|
manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"}
|
||||||
pv := &PluginVersion{Version: "0.45.0"}
|
pv := &plugintypes.Version{Version: "0.45.0"}
|
||||||
plat := &PluginPlatform{
|
plat := &plugintypes.Platform{
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
Arch: "amd64",
|
Arch: "amd64",
|
||||||
URL: server.URL + "/archive.tar.gz",
|
URL: server.URL + "/archive.tar.gz",
|
||||||
@@ -746,9 +748,9 @@ func TestInstallExtractPathZip(t *testing.T) {
|
|||||||
|
|
||||||
pluginDir := t.TempDir()
|
pluginDir := t.TempDir()
|
||||||
|
|
||||||
manifest := &PluginManifest{Name: "operator", Bin: "flux-operator"}
|
manifest := &plugintypes.Manifest{Name: "operator", Bin: "flux-operator"}
|
||||||
pv := &PluginVersion{Version: "0.45.0"}
|
pv := &plugintypes.Version{Version: "0.45.0"}
|
||||||
plat := &PluginPlatform{
|
plat := &plugintypes.Platform{
|
||||||
OS: "linux",
|
OS: "linux",
|
||||||
Arch: "amd64",
|
Arch: "amd64",
|
||||||
URL: server.URL + "/archive.zip",
|
URL: server.URL + "/archive.zip",
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ limitations under the License.
|
|||||||
|
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
plugintypes "github.com/fluxcd/flux2/v2/pkg/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SkipReasonManual = "manually installed"
|
SkipReasonManual = "manually installed"
|
||||||
SkipReasonUpToDate = "already up to date"
|
SkipReasonUpToDate = "already up to date"
|
||||||
@@ -41,13 +45,13 @@ type UpdateResult struct {
|
|||||||
SkipReason string
|
SkipReason string
|
||||||
|
|
||||||
// Manifest is the resolved plugin manifest for the update.
|
// Manifest is the resolved plugin manifest for the update.
|
||||||
Manifest *PluginManifest
|
Manifest *plugintypes.Manifest
|
||||||
|
|
||||||
// Version is the resolved target version for the update.
|
// Version is the resolved target version for the update.
|
||||||
Version *PluginVersion
|
Version *plugintypes.Version
|
||||||
|
|
||||||
// Platform is the resolved platform entry for the update.
|
// Platform is the resolved platform entry for the update.
|
||||||
Platform *PluginPlatform
|
Platform *plugintypes.Platform
|
||||||
|
|
||||||
// Err is set when the update check itself failed.
|
// Err is set when the update check itself failed.
|
||||||
Err error
|
Err error
|
||||||
|
|||||||
135
pkg/plugin/types.go
Normal file
135
pkg/plugin/types.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 The Flux authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package plugin defines the public types for the Flux CLI plugin system.
|
||||||
|
// These types represent the plugin catalog schema (cli.fluxcd.io/v1beta1)
|
||||||
|
// and are safe for use by external consumers.
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
const (
|
||||||
|
// APIVersion is the plugin manifest schema version.
|
||||||
|
APIVersion = "cli.fluxcd.io/v1beta1"
|
||||||
|
|
||||||
|
// PluginKind is the kind for plugin manifests.
|
||||||
|
PluginKind = "Plugin"
|
||||||
|
|
||||||
|
// CatalogKind is the kind for the plugin catalog.
|
||||||
|
CatalogKind = "PluginCatalog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manifest represents a single plugin's manifest from the catalog.
|
||||||
|
type Manifest struct {
|
||||||
|
// APIVersion is the manifest schema version (e.g. "cli.fluxcd.io/v1beta1").
|
||||||
|
APIVersion string `json:"apiVersion"`
|
||||||
|
|
||||||
|
// Kind is the manifest type, must be "Plugin".
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
|
||||||
|
// Name is the plugin name used in "flux <name>" invocations.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Description is a short human-readable summary of the plugin.
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Homepage is the URL to the plugin's documentation site.
|
||||||
|
Homepage string `json:"homepage,omitempty"`
|
||||||
|
|
||||||
|
// Source is the URL to the plugin's source repository.
|
||||||
|
Source string `json:"source,omitempty"`
|
||||||
|
|
||||||
|
// Bin is the binary name inside archives and the installed filename
|
||||||
|
// (e.g. "flux-operator"). On Windows ".exe" is appended automatically.
|
||||||
|
Bin string `json:"bin"`
|
||||||
|
|
||||||
|
// Versions lists available versions, newest first.
|
||||||
|
Versions []Version `json:"versions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version represents a version entry in a plugin manifest.
|
||||||
|
type Version struct {
|
||||||
|
// Version is the semantic version string (e.g. "0.45.0").
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// Platforms lists the platform-specific binaries for this version.
|
||||||
|
Platforms []Platform `json:"platforms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform represents a platform-specific binary entry.
|
||||||
|
type Platform struct {
|
||||||
|
// OS is the target operating system (e.g. "darwin", "linux", "windows").
|
||||||
|
OS string `json:"os"`
|
||||||
|
|
||||||
|
// Arch is the target architecture (e.g. "amd64", "arm64").
|
||||||
|
Arch string `json:"arch"`
|
||||||
|
|
||||||
|
// URL is the download URL for the archive or binary.
|
||||||
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
// Checksum is the expected digest in "<algorithm>:<hex>" format
|
||||||
|
// (e.g. "sha256:cd85d5d84d264...").
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
|
|
||||||
|
// ExtractPath overrides the default binary lookup name inside archives.
|
||||||
|
// When set, it is matched as an exact path within the archive (e.g.
|
||||||
|
// "bin/flux-operator"). When empty, the archive is searched by the
|
||||||
|
// base name derived from the manifest's Bin field.
|
||||||
|
ExtractPath string `json:"extractPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catalog represents the generated catalog.yaml file.
|
||||||
|
type Catalog struct {
|
||||||
|
// APIVersion is the catalog schema version (e.g. "cli.fluxcd.io/v1beta1").
|
||||||
|
APIVersion string `json:"apiVersion"`
|
||||||
|
|
||||||
|
// Kind is the catalog type, must be "PluginCatalog".
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
|
||||||
|
// Plugins lists all available plugins in the catalog.
|
||||||
|
Plugins []CatalogEntry `json:"plugins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CatalogEntry is a single entry in the plugin catalog.
|
||||||
|
type CatalogEntry struct {
|
||||||
|
// Name is the plugin name.
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Description is a short human-readable summary of the plugin.
|
||||||
|
Description string `json:"description"`
|
||||||
|
|
||||||
|
// Homepage is the URL to the plugin's documentation site.
|
||||||
|
Homepage string `json:"homepage"`
|
||||||
|
|
||||||
|
// Source is the URL to the plugin's source repository.
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// License is the SPDX license identifier (e.g. "Apache-2.0").
|
||||||
|
License string `json:"license"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receipt records what was installed for a plugin.
|
||||||
|
type Receipt struct {
|
||||||
|
// Name is the plugin name (e.g. "operator").
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
// Version is the installed semantic version.
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// InstalledAt is the RFC 3339 timestamp of the installation.
|
||||||
|
InstalledAt string `json:"installedAt"`
|
||||||
|
|
||||||
|
// Platform records the platform-specific details used for installation.
|
||||||
|
Platform Platform `json:"platform"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user