From 43597e416883994f8e616cf2ac65254d6b62a4aa Mon Sep 17 00:00:00 2001 From: Matt Joyce Date: Sun, 1 Dec 2024 18:04:11 +1100 Subject: [PATCH] Extension Registry Refinement - Successfully implemented path-based registry storage - Moved to storing paths instead of full configurations - Implemented proper hash verification for both configs and executables - Registry format now clean and minimal. File-Based Output Implementation - Successfully implemented file-based output handling - Demonstrated clean interface requiring only path output - Properly handles cleanup of temporary files - Verified working with both local and remote operations --- plugins/template/extension_registry.go | 158 +++++++++++++++++-------- 1 file changed, 108 insertions(+), 50 deletions(-) diff --git a/plugins/template/extension_registry.go b/plugins/template/extension_registry.go index 6da115ae..228d8de0 100644 --- a/plugins/template/extension_registry.go +++ b/plugins/template/extension_registry.go @@ -34,16 +34,24 @@ type OperationConfig struct { CmdTemplate string `yaml:"cmd_template"` } + + +// RegistryEntry represents a registered extension +type RegistryEntry struct { + ConfigPath string `yaml:"config_path"` + ConfigHash string `yaml:"config_hash"` + ExecutableHash string `yaml:"executable_hash"` +} + type ExtensionRegistry struct { configDir string registry struct { - Extensions map[string]*ExtensionDefinition - ConfigHashes map[string]string - ExecutableHashes map[string]string + Extensions map[string]*RegistryEntry `yaml:"extensions"` } } + // Helper methods for Config access func (e *ExtensionDefinition) GetOutputMethod() string { if output, ok := e.Config["output"].(map[string]interface{}); ok { @@ -74,26 +82,20 @@ func (e *ExtensionDefinition) IsCleanupEnabled() bool { func NewExtensionRegistry(configDir string) *ExtensionRegistry { - r := &ExtensionRegistry{ - configDir: configDir, - } - r.registry.Extensions = make(map[string]*ExtensionDefinition) - r.registry.ConfigHashes = make(map[string]string) - r.registry.ExecutableHashes = make(map[string]string) - - // Ensure extensions directory exists - r.ensureConfigDir() - - // Load existing registry if it exists - if err := r.loadRegistry(); err != nil { - // Since we're in a constructor, we can't return error - // Log it if we have logging, but continue with empty registry - if Debug { - fmt.Printf("Warning: could not load extension registry: %v\n", err) - } - } - - return r + r := &ExtensionRegistry{ + configDir: configDir, + } + r.registry.Extensions = make(map[string]*RegistryEntry) + + r.ensureConfigDir() + + if err := r.loadRegistry(); err != nil { + if Debug { + fmt.Printf("Warning: could not load extension registry: %v\n", err) + } + } + + return r } func (r *ExtensionRegistry) ensureConfigDir() error { @@ -102,7 +104,7 @@ func (r *ExtensionRegistry) ensureConfigDir() error { } func (r *ExtensionRegistry) Register(configPath string) error { - // Read and parse the extension definition + // Read and parse the extension definition to verify it data, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) @@ -113,22 +115,30 @@ func (r *ExtensionRegistry) Register(configPath string) error { return fmt.Errorf("failed to parse config file: %w", err) } - // Verify Executable exists + // Verify executable exists if _, err := os.Stat(ext.Executable); err != nil { - return fmt.Errorf("Executable not found: %w", err) + return fmt.Errorf("executable not found: %w", err) } - // Calculate hashes using template package functions - configHash := ComputeStringHash(string(data)) - ExecutableHash, err := ComputeHash(ext.Executable) + // Get absolute path to config + absPath, err := filepath.Abs(configPath) if err != nil { - return fmt.Errorf("failed to hash Executable: %w", err) + return fmt.Errorf("failed to get absolute path: %w", err) } - // Store extension and hashes - r.registry.Extensions[ext.Name] = &ext - r.registry.ConfigHashes[ext.Name] = configHash - r.registry.ExecutableHashes[ext.Name] = ExecutableHash + // Calculate hashes + configHash := ComputeStringHash(string(data)) + executableHash, err := ComputeHash(ext.Executable) + if err != nil { + return fmt.Errorf("failed to hash executable: %w", err) + } + + // Store entry + r.registry.Extensions[ext.Name] = &RegistryEntry{ + ConfigPath: absPath, + ConfigHash: configHash, + ExecutableHash: executableHash, + } return r.saveRegistry() } @@ -139,49 +149,97 @@ func (r *ExtensionRegistry) Remove(name string) error { } delete(r.registry.Extensions, name) - delete(r.registry.ConfigHashes, name) - delete(r.registry.ExecutableHashes, name) return r.saveRegistry() } func (r *ExtensionRegistry) Verify(name string) error { - ext, exists := r.registry.Extensions[name] + // Get the registry entry + entry, exists := r.registry.Extensions[name] if !exists { return fmt.Errorf("extension %s not found", name) } - // Verify Executable hash using template package function - currentExecutableHash, err := ComputeHash(ext.Executable) + // Load and parse the config file + data, err := os.ReadFile(entry.ConfigPath) if err != nil { - return fmt.Errorf("failed to verify Executable: %w", err) + return fmt.Errorf("failed to read config file: %w", err) } - if currentExecutableHash != r.registry.ExecutableHashes[name] { - return fmt.Errorf("Executable hash mismatch for %s", name) + // Verify config hash + currentConfigHash := ComputeStringHash(string(data)) + if currentConfigHash != entry.ConfigHash { + return fmt.Errorf("config file hash mismatch for %s", name) + } + + // Parse to get executable path + var ext ExtensionDefinition + if err := yaml.Unmarshal(data, &ext); err != nil { + return fmt.Errorf("failed to parse config file: %w", err) + } + + // Verify executable hash + currentExecutableHash, err := ComputeHash(ext.Executable) + if err != nil { + return fmt.Errorf("failed to verify executable: %w", err) + } + + if currentExecutableHash != entry.ExecutableHash { + return fmt.Errorf("executable hash mismatch for %s", name) } return nil } func (r *ExtensionRegistry) GetExtension(name string) (*ExtensionDefinition, error) { - ext, exists := r.registry.Extensions[name] + entry, exists := r.registry.Extensions[name] if !exists { return nil, fmt.Errorf("extension %s not found", name) } - - if err := r.Verify(name); err != nil { - return nil, err + + // Read current config file + data, err := os.ReadFile(entry.ConfigPath) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) } - - return ext, nil + + // Verify config hash + currentHash := ComputeStringHash(string(data)) + if currentHash != entry.ConfigHash { + return nil, fmt.Errorf("config file hash mismatch for %s", name) + } + + // Parse config + var ext ExtensionDefinition + if err := yaml.Unmarshal(data, &ext); err != nil { + return nil, fmt.Errorf("failed to parse config file: %w", err) + } + + // Verify executable hash + currentExecHash, err := ComputeHash(ext.Executable) + if err != nil { + return nil, fmt.Errorf("failed to verify executable: %w", err) + } + + if currentExecHash != entry.ExecutableHash { + return nil, fmt.Errorf("executable hash mismatch for %s", name) + } + + return &ext, nil } + func (r *ExtensionRegistry) ListExtensions() ([]*ExtensionDefinition, error) { - exts := make([]*ExtensionDefinition, 0, len(r.registry.Extensions)) - for _, ext := range r.registry.Extensions { + var exts []*ExtensionDefinition + + for name := range r.registry.Extensions { + ext, err := r.GetExtension(name) + if err != nil { + return nil, fmt.Errorf("failed to load extension %s: %w", name, err) + } exts = append(exts, ext) } + return exts, nil }