<commit_before>package plugin

import (
	"errors"
	"fmt"
	"net/rpc"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/cloudfoundry/cli/cf/actors/plugin_repo"
	"github.com/cloudfoundry/cli/cf/command_registry"
	"github.com/cloudfoundry/cli/cf/configuration/core_config"
	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/requirements"
	"github.com/cloudfoundry/cli/cf/terminal"
	"github.com/cloudfoundry/cli/fileutils"
	"github.com/cloudfoundry/cli/flags"
	"github.com/cloudfoundry/cli/flags/flag"
	"github.com/cloudfoundry/cli/plugin"
	"github.com/cloudfoundry/cli/utils"

	clipr "github.com/cloudfoundry-incubator/cli-plugin-repo/models"
	rpcService "github.com/cloudfoundry/cli/plugin/rpc"
)

type PluginInstall struct {
	ui           terminal.UI
	config       core_config.Reader
	pluginConfig plugin_config.PluginConfiguration
	pluginRepo   plugin_repo.PluginRepo
	checksum     utils.Sha1Checksum
	rpcService   *rpcService.CliRpcService
}

func init() {
	command_registry.Register(&PluginInstall{})
}

func (cmd *PluginInstall) MetaData() command_registry.CommandMetadata {
	fs := make(map[string]flags.FlagSet)
	fs["r"] = &cliFlags.StringFlag{Name: "r", Usage: T("repo name where the plugin binary is located")}

	return command_registry.CommandMetadata{
		Name:        "install-plugin",
		Description: T("Install the plugin defined in command argument"),
		Usage: T(`CF_NAME install-plugin URL or LOCAL-PATH/TO/PLUGIN [-r REPO_NAME]

The command will download the plugin binary from repository if '-r' is provided

EXAMPLE:
   cf install-plugin https://github.com/cf-experimental/plugin-foobar
   cf install-plugin ~/Downloads/plugin-foobar
   cf install-plugin plugin-echo -r My-Repo 
`),
		Flags:     fs,
		TotalArgs: 1,
	}
}

func (cmd *PluginInstall) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) (reqs []requirements.Requirement, err error) {
	if len(fc.Args()) != 1 {
		cmd.ui.Failed(T("Incorrect Usage. Requires an argument\n\n") + command_registry.Commands.CommandUsage("install-plugin"))
	}

	return
}

func (cmd *PluginInstall) SetDependency(deps command_registry.Dependency, pluginCall bool) command_registry.Command {
	cmd.ui = deps.Ui
	cmd.config = deps.Config
	cmd.pluginConfig = deps.PluginConfig
	cmd.pluginRepo = deps.PluginRepo
	cmd.checksum = deps.ChecksumUtil

	//reset rpc registration in case there is other running instance,
	//each service can only be registered once
	rpc.DefaultServer = rpc.NewServer()

	rpcService, err := rpcService.NewRpcService(deps.TeePrinter, deps.TeePrinter, deps.Config, deps.RepoLocator, rpcService.NewNonCodegangstaRunner())
	if err != nil {
		cmd.ui.Failed("Error initializing RPC service: " + err.Error())
	}

	cmd.rpcService = rpcService

	return cmd
}

type Installer interface {
	Install(inputSourceFilepath string) string
}

type PluginInstallerWithoutRepo struct {
	ui               terminal.UI
	pluginDownloader *PluginDownloader
	downloadFromPath downloadFromPath
	repoName         string
	Context          *InstallerContext
}

func (installer *PluginInstallerWithoutRepo) Install(inputSourceFilepath string) (outputSourceFilepath string) {
	if filepath.Dir(inputSourceFilepath) == "." {
		outputSourceFilepath = "./" + filepath.Clean(inputSourceFilepath)
	} else {
		outputSourceFilepath = inputSourceFilepath
	}

	installer.ui.Say("")
	if strings.HasPrefix(outputSourceFilepath, "https://") || strings.HasPrefix(outputSourceFilepath, "http://") ||
		strings.HasPrefix(outputSourceFilepath, "ftp://") || strings.HasPrefix(outputSourceFilepath, "ftps://") {
		installer.ui.Say(T("Attempting to download binary file from internet address..."))
		return installer.pluginDownloader.downloadFromPath(outputSourceFilepath)
	} else if !installer.ensureCandidatePluginBinaryExistsAtGivenPath(outputSourceFilepath) {
		installer.ui.Failed(T("File not found locally, make sure the file exists at given path {{.filepath}}", map[string]interface{}{"filepath": outputSourceFilepath}))
	}

	return outputSourceFilepath
}

type pluginReposFetcher func() []models.PluginRepo

type PluginInstallerWithRepo struct {
	ui               terminal.UI
	pluginDownloader *PluginDownloader
	downloadFromPath downloadFromPath
	repoName         string
	checksummer      utils.Sha1Checksum
	pluginRepo       plugin_repo.PluginRepo
	getPluginRepos   pluginReposFetcher
	Context          *InstallerContext
}

func (installer *PluginInstallerWithRepo) Install(inputSourceFilepath string) (outputSourceFilepath string) {
	outputSourceFilepath = inputSourceFilepath
	targetPluginName := strings.ToLower(inputSourceFilepath)

	installer.ui.Say(T("Looking up '{{.filePath}}' from repository '{{.repoName}}'", map[string]interface{}{"filePath": inputSourceFilepath, "repoName": installer.repoName}))

	repoModel, err := installer.getRepoFromConfig(installer.repoName)
	if err != nil {
		installer.ui.Failed(err.Error() + "\n" + T("Tip: use 'add-plugin-repo' to register the repo"))
	}

	pluginList, repoAry := installer.pluginRepo.GetPlugins([]models.PluginRepo{repoModel})
	if len(repoAry) != 0 {
		installer.ui.Failed(T("Error getting plugin metadata from repo: ") + repoAry[0])
	}

	found := false
	sha1 := ""
	for _, plugin := range findRepoCaseInsensity(pluginList, installer.repoName) {
		if strings.ToLower(plugin.Name) == targetPluginName {
			found = true
			outputSourceFilepath, sha1 = installer.pluginDownloader.downloadFromPlugin(plugin)

			installer.checksummer.SetFilePath(outputSourceFilepath)
			if !installer.checksummer.CheckSha1(sha1) {
				installer.ui.Failed(T("Downloaded plugin binary's checksum does not match repo metadata"))
			}
		}

	}
	if !found {
		installer.ui.Failed(outputSourceFilepath + T(" is not available in repo '") + installer.repoName + "'")
	}

	return outputSourceFilepath
}

type InstallerContext struct {
	pluginDownloader *PluginDownloader
	repoName         string
	checksummer      utils.Sha1Checksum
	pluginRepo       plugin_repo.PluginRepo
	ui               terminal.UI
	getPluginRepos   pluginReposFetcher
}

type downloadFromPath func(pluginSourceFilepath string, downloader fileutils.Downloader) string<commit_msg>Remove mutation of return val in repo install<commit_after>package plugin

import (
	"errors"
	"fmt"
	"net/rpc"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/cloudfoundry/cli/cf/actors/plugin_repo"
	"github.com/cloudfoundry/cli/cf/command_registry"
	"github.com/cloudfoundry/cli/cf/configuration/core_config"
	"github.com/cloudfoundry/cli/cf/configuration/plugin_config"
	. "github.com/cloudfoundry/cli/cf/i18n"
	"github.com/cloudfoundry/cli/cf/models"
	"github.com/cloudfoundry/cli/cf/requirements"
	"github.com/cloudfoundry/cli/cf/terminal"
	"github.com/cloudfoundry/cli/fileutils"
	"github.com/cloudfoundry/cli/flags"
	"github.com/cloudfoundry/cli/flags/flag"
	"github.com/cloudfoundry/cli/plugin"
	"github.com/cloudfoundry/cli/utils"

	clipr "github.com/cloudfoundry-incubator/cli-plugin-repo/models"
	rpcService "github.com/cloudfoundry/cli/plugin/rpc"
)

type PluginInstall struct {
	ui           terminal.UI
	config       core_config.Reader
	pluginConfig plugin_config.PluginConfiguration
	pluginRepo   plugin_repo.PluginRepo
	checksum     utils.Sha1Checksum
	rpcService   *rpcService.CliRpcService
}

func init() {
	command_registry.Register(&PluginInstall{})
}

func (cmd *PluginInstall) MetaData() command_registry.CommandMetadata {
	fs := make(map[string]flags.FlagSet)
	fs["r"] = &cliFlags.StringFlag{Name: "r", Usage: T("repo name where the plugin binary is located")}

	return command_registry.CommandMetadata{
		Name:        "install-plugin",
		Description: T("Install the plugin defined in command argument"),
		Usage: T(`CF_NAME install-plugin URL or LOCAL-PATH/TO/PLUGIN [-r REPO_NAME]

The command will download the plugin binary from repository if '-r' is provided

EXAMPLE:
   cf install-plugin https://github.com/cf-experimental/plugin-foobar
   cf install-plugin ~/Downloads/plugin-foobar
   cf install-plugin plugin-echo -r My-Repo 
`),
		Flags:     fs,
		TotalArgs: 1,
	}
}

func (cmd *PluginInstall) Requirements(requirementsFactory requirements.Factory, fc flags.FlagContext) (reqs []requirements.Requirement, err error) {
	if len(fc.Args()) != 1 {
		cmd.ui.Failed(T("Incorrect Usage. Requires an argument\n\n") + command_registry.Commands.CommandUsage("install-plugin"))
	}

	return
}

func (cmd *PluginInstall) SetDependency(deps command_registry.Dependency, pluginCall bool) command_registry.Command {
	cmd.ui = deps.Ui
	cmd.config = deps.Config
	cmd.pluginConfig = deps.PluginConfig
	cmd.pluginRepo = deps.PluginRepo
	cmd.checksum = deps.ChecksumUtil

	//reset rpc registration in case there is other running instance,
	//each service can only be registered once
	rpc.DefaultServer = rpc.NewServer()

	rpcService, err := rpcService.NewRpcService(deps.TeePrinter, deps.TeePrinter, deps.Config, deps.RepoLocator, rpcService.NewNonCodegangstaRunner())
	if err != nil {
		cmd.ui.Failed("Error initializing RPC service: " + err.Error())
	}

	cmd.rpcService = rpcService

	return cmd
}

type Installer interface {
	Install(inputSourceFilepath string) string
}

type PluginInstallerWithoutRepo struct {
	ui               terminal.UI
	pluginDownloader *PluginDownloader
	downloadFromPath downloadFromPath
	repoName         string
	Context          *InstallerContext
}

func (installer *PluginInstallerWithoutRepo) Install(inputSourceFilepath string) (outputSourceFilepath string) {
	if filepath.Dir(inputSourceFilepath) == "." {
		outputSourceFilepath = "./" + filepath.Clean(inputSourceFilepath)
	} else {
		outputSourceFilepath = inputSourceFilepath
	}

	installer.ui.Say("")
	if strings.HasPrefix(outputSourceFilepath, "https://") || strings.HasPrefix(outputSourceFilepath, "http://") ||
		strings.HasPrefix(outputSourceFilepath, "ftp://") || strings.HasPrefix(outputSourceFilepath, "ftps://") {
		installer.ui.Say(T("Attempting to download binary file from internet address..."))
		return installer.pluginDownloader.downloadFromPath(outputSourceFilepath)
	} else if !installer.ensureCandidatePluginBinaryExistsAtGivenPath(outputSourceFilepath) {
		installer.ui.Failed(T("File not found locally, make sure the file exists at given path {{.filepath}}", map[string]interface{}{"filepath": outputSourceFilepath}))
	}

	return outputSourceFilepath
}

type pluginReposFetcher func() []models.PluginRepo

type PluginInstallerWithRepo struct {
	ui               terminal.UI
	pluginDownloader *PluginDownloader
	downloadFromPath downloadFromPath
	repoName         string
	checksummer      utils.Sha1Checksum
	pluginRepo       plugin_repo.PluginRepo
	getPluginRepos   pluginReposFetcher
	Context          *InstallerContext
}

func (installer *PluginInstallerWithRepo) Install(inputSourceFilepath string) (outputSourceFilepath string) {
	targetPluginName := strings.ToLower(inputSourceFilepath)

	installer.ui.Say(T("Looking up '{{.filePath}}' from repository '{{.repoName}}'", map[string]interface{}{"filePath": inputSourceFilepath, "repoName": installer.repoName}))

	repoModel, err := installer.getRepoFromConfig(installer.repoName)
	if err != nil {
		installer.ui.Failed(err.Error() + "\n" + T("Tip: use 'add-plugin-repo' to register the repo"))
	}

	pluginList, repoAry := installer.pluginRepo.GetPlugins([]models.PluginRepo{repoModel})
	if len(repoAry) != 0 {
		installer.ui.Failed(T("Error getting plugin metadata from repo: ") + repoAry[0])
	}

	found := false
	sha1 := ""
	for _, plugin := range findRepoCaseInsensity(pluginList, installer.repoName) {
		if strings.ToLower(plugin.Name) == targetPluginName {
			found = true
			outputSourceFilepath, sha1 = installer.pluginDownloader.downloadFromPlugin(plugin)

			installer.checksummer.SetFilePath(outputSourceFilepath)
			if !installer.checksummer.CheckSha1(sha1) {
				installer.ui.Failed(T("Downloaded plugin binary's checksum does not match repo metadata"))
			}
		}

	}
	if !found {
		installer.ui.Failed(inputSourceFilepath + T(" is not available in repo '") + installer.repoName + "'")
	}

	return outputSourceFilepath
}

type InstallerContext struct {
	pluginDownloader *PluginDownloader
	repoName         string
	checksummer      utils.Sha1Checksum
	pluginRepo       plugin_repo.PluginRepo
	ui               terminal.UI
	getPluginRepos   pluginReposFetcher
}

type downloadFromPath func(pluginSourceFilepath string, downloader fileutils.Downloader) string
{'fraction_non_alphanumeric': 0.10981736980620464, 'fraction_numerical': 0.0020735305845761224, 'mean_word_length': 4.915094339622642, 'pattern_counts': {'":': 6, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1348982', 'n_tokens_mistral': 4080, 'n_tokens_neox': 3968, 'n_words': 800}