Add deploy command to upload and enable plugin bundles

- New deploy.go implements RunDeployCommand function
- Auto-discovers plugin bundle from ./dist/ folder based on manifest
- Supports --bundle-path flag for custom bundle location
- Reuses existing client connection logic for server authentication
- Updated main.go to register deploy command and add help documentation
- Follows existing patterns for error handling and structured logging

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Felipe M 2025-07-14 19:17:25 +02:00
parent 6639dad2d6
commit 278958d1e4
No known key found for this signature in database
GPG key ID: 52E5D65FCF99808A
3 changed files with 108 additions and 1 deletions

View file

@ -5,7 +5,7 @@
## Builds and installs the plugin to a server.
.PHONY: deploy
deploy: dist
./build/bin/pluginctl deploy --bundle-path dist/$(BUNDLE_NAME)
pluginctl deploy --bundle-path dist/$(BUNDLE_NAME)
## Builds and installs the plugin to a server, updating the webapp automatically when changed.
.PHONY: watch

View file

@ -52,6 +52,8 @@ func runCommand(command string, args []string, pluginPath string) error {
return runDisableCommand(args, pluginPath)
case "reset":
return runResetCommand(args, pluginPath)
case "deploy":
return runDeployCommand(args, pluginPath)
case "updateassets":
return runUpdateAssetsCommand(args, pluginPath)
case "manifest":
@ -103,6 +105,10 @@ func runLogsCommand(args []string, pluginPath string) error {
return pluginctl.RunLogsCommand(args, pluginPath)
}
func runDeployCommand(args []string, pluginPath string) error {
return pluginctl.RunDeployCommand(args, pluginPath)
}
func showUsage() {
usageText := `pluginctl - Mattermost Plugin Development CLI
@ -117,6 +123,7 @@ Commands:
enable Enable plugin from current directory in Mattermost server
disable Disable plugin from current directory in Mattermost server
reset Reset plugin from current directory (disable then enable)
deploy Upload and enable plugin bundle to Mattermost server
updateassets Update plugin files from embedded assets
manifest Get plugin manifest information (id, version, has_server, has_webapp, check)
logs View plugin logs (use --watch to follow logs in real-time)
@ -129,6 +136,8 @@ Examples:
pluginctl enable # Enable plugin from current directory
pluginctl disable # Disable plugin from current directory
pluginctl reset # Reset plugin from current directory (disable then enable)
pluginctl deploy # Upload and enable plugin bundle from ./dist/
pluginctl deploy --bundle-path ./bundle.tar.gz # Deploy specific bundle file
pluginctl updateassets # Update plugin files from embedded assets
pluginctl manifest id # Get plugin ID
pluginctl manifest version # Get plugin version

98
deploy.go Normal file
View file

@ -0,0 +1,98 @@
package pluginctl
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/mattermost/mattermost/server/public/model"
)
func RunDeployCommand(args []string, pluginPath string) error {
var bundlePath string
// Parse flags
i := 0
for i < len(args) {
switch args[i] {
case "--bundle-path":
if i+1 >= len(args) {
return fmt.Errorf("--bundle-path flag requires a value")
}
bundlePath = args[i+1]
i += 2
default:
i++
}
}
// If no bundle path provided, auto-discover from dist folder
if bundlePath == "" {
manifest, err := LoadPluginManifestFromPath(pluginPath)
if err != nil {
return fmt.Errorf("failed to load plugin manifest: %w", err)
}
expectedBundleName := fmt.Sprintf("%s-%s.tar.gz", manifest.Id, manifest.Version)
bundlePath = filepath.Join(pluginPath, "dist", expectedBundleName)
if _, err := os.Stat(bundlePath); os.IsNotExist(err) {
return fmt.Errorf("bundle not found at %s - run 'make bundle' to build the plugin first", bundlePath)
}
}
// Validate bundle file exists
if _, err := os.Stat(bundlePath); os.IsNotExist(err) {
return fmt.Errorf("bundle file not found: %s", bundlePath)
}
// Load manifest to get plugin ID
manifest, err := LoadPluginManifestFromPath(pluginPath)
if err != nil {
return fmt.Errorf("failed to load plugin manifest: %w", err)
}
pluginID := manifest.Id
if pluginID == "" {
return fmt.Errorf("plugin ID not found in manifest")
}
ctx, cancel := context.WithTimeout(context.Background(), commandTimeout)
defer cancel()
client, err := getClient(ctx)
if err != nil {
return err
}
return deployPlugin(ctx, client, pluginID, bundlePath)
}
func deployPlugin(ctx context.Context, client *model.Client4, pluginID, bundlePath string) error {
pluginBundle, err := os.Open(bundlePath)
if err != nil {
return fmt.Errorf("failed to open bundle file %s: %w", bundlePath, err)
}
defer func() {
if closeErr := pluginBundle.Close(); closeErr != nil {
Logger.Error("Failed to close plugin bundle", "error", closeErr)
}
}()
Logger.Info("Uploading plugin bundle", "bundle_path", bundlePath)
_, _, err = client.UploadPluginForced(ctx, pluginBundle)
if err != nil {
return fmt.Errorf("failed to upload plugin bundle: %w", err)
}
Logger.Info("Enabling plugin", "plugin_id", pluginID)
_, err = client.EnablePlugin(ctx, pluginID)
if err != nil {
return fmt.Errorf("failed to enable plugin: %w", err)
}
Logger.Info("Plugin deployed successfully", "plugin_id", pluginID)
return nil
}