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:
parent
6639dad2d6
commit
278958d1e4
3 changed files with 108 additions and 1 deletions
|
@ -5,7 +5,7 @@
|
||||||
## Builds and installs the plugin to a server.
|
## Builds and installs the plugin to a server.
|
||||||
.PHONY: deploy
|
.PHONY: deploy
|
||||||
deploy: dist
|
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.
|
## Builds and installs the plugin to a server, updating the webapp automatically when changed.
|
||||||
.PHONY: watch
|
.PHONY: watch
|
||||||
|
|
|
@ -52,6 +52,8 @@ func runCommand(command string, args []string, pluginPath string) error {
|
||||||
return runDisableCommand(args, pluginPath)
|
return runDisableCommand(args, pluginPath)
|
||||||
case "reset":
|
case "reset":
|
||||||
return runResetCommand(args, pluginPath)
|
return runResetCommand(args, pluginPath)
|
||||||
|
case "deploy":
|
||||||
|
return runDeployCommand(args, pluginPath)
|
||||||
case "updateassets":
|
case "updateassets":
|
||||||
return runUpdateAssetsCommand(args, pluginPath)
|
return runUpdateAssetsCommand(args, pluginPath)
|
||||||
case "manifest":
|
case "manifest":
|
||||||
|
@ -103,6 +105,10 @@ func runLogsCommand(args []string, pluginPath string) error {
|
||||||
return pluginctl.RunLogsCommand(args, pluginPath)
|
return pluginctl.RunLogsCommand(args, pluginPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runDeployCommand(args []string, pluginPath string) error {
|
||||||
|
return pluginctl.RunDeployCommand(args, pluginPath)
|
||||||
|
}
|
||||||
|
|
||||||
func showUsage() {
|
func showUsage() {
|
||||||
usageText := `pluginctl - Mattermost Plugin Development CLI
|
usageText := `pluginctl - Mattermost Plugin Development CLI
|
||||||
|
|
||||||
|
@ -117,6 +123,7 @@ Commands:
|
||||||
enable Enable plugin from current directory in Mattermost server
|
enable Enable plugin from current directory in Mattermost server
|
||||||
disable Disable 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)
|
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
|
updateassets Update plugin files from embedded assets
|
||||||
manifest Get plugin manifest information (id, version, has_server, has_webapp, check)
|
manifest Get plugin manifest information (id, version, has_server, has_webapp, check)
|
||||||
logs View plugin logs (use --watch to follow logs in real-time)
|
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 enable # Enable plugin from current directory
|
||||||
pluginctl disable # Disable plugin from current directory
|
pluginctl disable # Disable plugin from current directory
|
||||||
pluginctl reset # Reset plugin from current directory (disable then enable)
|
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 updateassets # Update plugin files from embedded assets
|
||||||
pluginctl manifest id # Get plugin ID
|
pluginctl manifest id # Get plugin ID
|
||||||
pluginctl manifest version # Get plugin version
|
pluginctl manifest version # Get plugin version
|
||||||
|
|
98
deploy.go
Normal file
98
deploy.go
Normal 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
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue