github.com/hashicorp/packer@v1.14.3/command/plugins_remove.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package command
     5  
     6  import (
     7  	"context"
     8  	"crypto/sha256"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  
    16  	"github.com/hashicorp/go-version"
    17  	"github.com/hashicorp/hcl/v2"
    18  	"github.com/hashicorp/packer/hcl2template/addrs"
    19  	"github.com/hashicorp/packer/packer"
    20  	plugingetter "github.com/hashicorp/packer/packer/plugin-getter"
    21  	"github.com/mitchellh/cli"
    22  )
    23  
    24  type PluginsRemoveCommand struct {
    25  	Meta
    26  }
    27  
    28  func (c *PluginsRemoveCommand) Synopsis() string {
    29  	return "Remove Packer plugins [matching a version]"
    30  }
    31  
    32  func (c *PluginsRemoveCommand) Help() string {
    33  	helpText := `
    34  Usage: packer plugins remove <plugin> [<version constraint>]
    35  
    36    This command will remove one or more installed Packer plugins.
    37  
    38    To remove a plugin matching a version constraint for the current OS and architecture.
    39  
    40        packer plugins remove github.com/hashicorp/happycloud v1.2.3
    41  
    42    To remove all versions of a plugin for the current OS and architecture omit the version constraint.
    43  
    44        packer plugins remove github.com/hashicorp/happycloud
    45  
    46    To remove a single plugin binary from the Packer plugin directory specify the absolute path to an installed binary. This syntax does not allow for version matching.
    47  
    48        packer plugins remove ~/.config/plugins/github.com/hashicorp/happycloud/packer-plugin-happycloud_v1.0.0_x5.0_linux_amd64
    49  `
    50  
    51  	return strings.TrimSpace(helpText)
    52  }
    53  
    54  func (c *PluginsRemoveCommand) Run(args []string) int {
    55  	ctx, cleanup := handleTermInterrupt(c.Ui)
    56  	defer cleanup()
    57  
    58  	return c.RunContext(ctx, args)
    59  }
    60  
    61  // deletePluginBinary removes a local plugin binary, and its related checksum file.
    62  func deletePluginBinary(pluginPath string) error {
    63  	if err := os.Remove(pluginPath); err != nil {
    64  		return err
    65  	}
    66  	shasumFile := fmt.Sprintf("%s_SHA256SUM", pluginPath)
    67  
    68  	if _, err := os.Stat(shasumFile); err != nil {
    69  		log.Printf("[INFO] No SHA256SUM file to remove for the plugin, ignoring.")
    70  		return nil
    71  	}
    72  
    73  	return os.Remove(shasumFile)
    74  }
    75  
    76  func (c *PluginsRemoveCommand) RunContext(buildCtx context.Context, args []string) int {
    77  	if len(args) < 1 || len(args) > 2 {
    78  		return cli.RunResultHelp
    79  	}
    80  
    81  	pluginDir, err := packer.PluginFolder()
    82  	if err != nil {
    83  		return writeDiags(c.Ui, nil, hcl.Diagnostics{
    84  			&hcl.Diagnostic{
    85  				Severity: hcl.DiagError,
    86  				Summary:  "Failed to get the plugin directory",
    87  				Detail: fmt.Sprintf(
    88  					"The directory in which plugins are installed could not be fetched from the environment. This is likely a Packer bug. Error: %s",
    89  					err),
    90  			},
    91  		})
    92  	}
    93  
    94  	if filepath.IsAbs(args[0]) {
    95  		if len(args) != 1 {
    96  			c.Ui.Error("Unsupported: no version constraint may be specified with a local plugin path.\n")
    97  			return cli.RunResultHelp
    98  		}
    99  
   100  		if !strings.Contains(args[0], pluginDir) {
   101  			return writeDiags(c.Ui, nil, hcl.Diagnostics{
   102  				&hcl.Diagnostic{
   103  					Severity: hcl.DiagError,
   104  					Summary:  "Invalid plugin location",
   105  					Detail: fmt.Sprintf(
   106  						"The path %q is not under the plugin directory inferred by Packer (%s) and will not be removed.",
   107  						args[0],
   108  						pluginDir),
   109  				},
   110  			})
   111  		}
   112  
   113  		log.Printf("will delete plugin located at %q", args[0])
   114  		err := deletePluginBinary(args[0])
   115  		if err != nil {
   116  			return writeDiags(c.Ui, nil, hcl.Diagnostics{
   117  				&hcl.Diagnostic{
   118  					Severity: hcl.DiagError,
   119  					Summary:  "Failed to delete plugin",
   120  					Detail:   fmt.Sprintf("The plugin %q failed to be deleted with the following error: %q", args[0], err),
   121  				},
   122  			})
   123  		}
   124  
   125  		c.Ui.Say(args[0])
   126  
   127  		return 0
   128  	}
   129  
   130  	opts := plugingetter.ListInstallationsOptions{
   131  		PluginDirectory: c.Meta.CoreConfig.Components.PluginConfig.PluginDirectory,
   132  		BinaryInstallationOptions: plugingetter.BinaryInstallationOptions{
   133  			OS:   runtime.GOOS,
   134  			ARCH: runtime.GOARCH,
   135  			Checksummers: []plugingetter.Checksummer{
   136  				{Type: "sha256", Hash: sha256.New()},
   137  			},
   138  		},
   139  	}
   140  
   141  	if runtime.GOOS == "windows" && opts.Ext == "" {
   142  		opts.BinaryInstallationOptions.Ext = ".exe"
   143  	}
   144  
   145  	plugin, err := addrs.ParsePluginSourceString(args[0])
   146  	if err != nil {
   147  		c.Ui.Errorf("Invalid source string %q: %s", args[0], err)
   148  		return 1
   149  	}
   150  
   151  	// a plugin requirement that matches them all
   152  	pluginRequirement := plugingetter.Requirement{
   153  		Identifier: plugin,
   154  	}
   155  
   156  	if len(args) > 1 {
   157  		constraints, err := version.NewConstraint(args[1])
   158  		if err != nil {
   159  			c.Ui.Error(err.Error())
   160  			return 1
   161  		}
   162  		pluginRequirement.VersionConstraints = constraints
   163  	}
   164  
   165  	installations, err := pluginRequirement.ListInstallations(opts)
   166  	if err != nil {
   167  		c.Ui.Error(err.Error())
   168  		return 1
   169  	}
   170  	for _, installation := range installations {
   171  		err := deletePluginBinary(installation.BinaryPath)
   172  		if err != nil {
   173  			c.Ui.Error(fmt.Sprintf("Failed to remove plugin %q: %q", installation.BinaryPath, err))
   174  			continue
   175  		}
   176  		c.Ui.Message(installation.BinaryPath)
   177  	}
   178  
   179  	if len(installations) == 0 {
   180  		errMsg := fmt.Sprintf("No installed plugin found matching the plugin constraints %s", args[0])
   181  		if len(args) == 2 {
   182  			errMsg = fmt.Sprintf("%s %s", errMsg, args[1])
   183  		}
   184  		c.Ui.Error(errMsg)
   185  		return 1
   186  	}
   187  
   188  	return 0
   189  }