This change implements basic plugin management, but we do not yet actually use the plugins for anything (that comes next). Plugins are stored in `~/.pulumi/plugins`, and are expected to be in the format `pulumi-<KIND>-<NAME>-v<VERSION>[.exe]`. The KIND is one of `analyzer`, `language`, or `resource`, the NAME is a hyphen- delimited name (e.g., `aws` or `foo-bar`), and VERSION is the plugin's semantic version (e.g., `0.9.11`, `1.3.7-beta.a736cf`, etc). This commit includes four new CLI commands: * `pulumi plugin` is the top-level plugin command. It does nothing but show the help text for associated child commands. * `pulumi plugin install` can be used to install plugins manually. If run with no additional arguments, it will compute the set of plugins used by the current project, and download them all. It may be run to explicitly download a single plugin, however, by invoking it as `pulumi plugin install KIND NAME VERSION`. For example, `pulumi plugin install resource aws v0.9.11`. By default, this command uses the cloud backend in the usual way to perform the download, although a separate URL may be given with --cloud-url, just like all other commands that interact with our backend service. * `pulumi plugin ls` lists all plugins currently installed in the plugin cache. It displays some useful statistics, like the size of the plugin, when it was installed, when it was last used, and so on. It sorts the display alphabetically by plugin name, and for plugins with multiple versions, it shows the newest at the top. The command also summarizes how much disk space is currently being consumed by the plugin cache. There are no filtering capabilities yet. * `pulumi plugin prune` will delete plugins from the cache. By default, when run with no arguments, it will delete everything. It may be run with additional arguments, KIND, NAME, and VERSION, each one getting more specific about what it will delete. For instance, `pulumi plugin prune resource aws` will delete all AWS plugin versions, while `pulumi plugin prune resource aws <0.9` will delete all AWS plugins before version 0.9. Unless --yes is passed, the command will confirm the deletion with a count of how many plugins will be affected by the command. We do not yet actually download plugins on demand yet. That will come in a subsequent change.
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
|
|
|
package workspace
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/pulumi/pulumi/pkg/encoding"
|
|
"github.com/pulumi/pulumi/pkg/util/fsutil"
|
|
)
|
|
|
|
const (
|
|
BookkeepingDir = ".pulumi" // the name of our bookeeping folder, we store state here (like .git for git).
|
|
ConfigDir = "config" // the name of the folder that holds local configuration information.
|
|
GitDir = ".git" // the name of the folder git uses to store information.
|
|
HistoryDir = "history" // the name of the directory that holds historical information for projects.
|
|
PluginDir = "plugins" // the name of the directory containing plugins.
|
|
StackDir = "stacks" // the name of the directory that holds stack information for projects.
|
|
WorkspaceDir = "workspaces" // the name of the directory that holds workspace information for projects.
|
|
|
|
IgnoreFile = ".pulumiignore" // the name of the file that we use to control what to upload to the service.
|
|
ProjectFile = "Pulumi" // the base name of a project file.
|
|
RepoFile = "settings.json" // the name of the file that holds information specific to the entire repository.
|
|
WorkspaceFile = "workspace.json" // the name of the file that holds workspace information.
|
|
)
|
|
|
|
// DetectProjectPath locates the closest project from the current working directory, or an error if not found.
|
|
func DetectProjectPath() (string, error) {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
path, err := DetectProjectPathFrom(dir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return path, nil
|
|
}
|
|
|
|
// DetectProjectPathFrom locates the closest project from the given path, searching "upwards" in the directory
|
|
// hierarchy. If no project is found, an empty path is returned. If problems are detected, they are logged to
|
|
// the diag.Sink.
|
|
func DetectProjectPathFrom(path string) (string, error) {
|
|
return fsutil.WalkUp(path, isProject, func(s string) bool {
|
|
return !isRepositoryFolder(filepath.Join(s, BookkeepingDir))
|
|
})
|
|
}
|
|
|
|
// DetectProject loads the closest project from the current working directory, or an error if not found.
|
|
func DetectProject() (*Project, error) {
|
|
proj, _, err := DetectProjectAndPath()
|
|
return proj, err
|
|
}
|
|
|
|
// DetectProjectAndPath loads the closest package from the current working directory, or an error if not found. It
|
|
// also returns the path where the package was found.
|
|
func DetectProjectAndPath() (*Project, string, error) {
|
|
path, err := DetectProjectPath()
|
|
if err != nil {
|
|
return nil, "", err
|
|
} else if path == "" {
|
|
return nil, "", errors.Errorf("no Pulumi project found in the current working directory")
|
|
}
|
|
|
|
proj, err := LoadProject(path)
|
|
return proj, path, err
|
|
}
|
|
|
|
// SaveProject saves the project file on top of the existing one, using the standard location.
|
|
func SaveProject(proj *Project) error {
|
|
path, err := DetectProjectPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return proj.Save(path)
|
|
}
|
|
|
|
func isGitFolder(path string) bool {
|
|
info, err := os.Stat(path)
|
|
return err == nil && info.IsDir() && info.Name() == ".git"
|
|
}
|
|
|
|
func isRepositoryFolder(path string) bool {
|
|
info, err := os.Stat(path)
|
|
if err == nil && info.IsDir() && info.Name() == BookkeepingDir {
|
|
// make sure it has a settings.json file in it
|
|
info, err := os.Stat(filepath.Join(path, RepoFile))
|
|
return err == nil && !info.IsDir()
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isProject returns true if the path references what appears to be a valid project. If problems are detected -- like
|
|
// an incorrect extension -- they are logged to the provided diag.Sink (if non-nil).
|
|
func isProject(path string) bool {
|
|
return isMarkupFile(path, ProjectFile)
|
|
}
|
|
|
|
func isMarkupFile(path string, expect string) bool {
|
|
info, err := os.Stat(path)
|
|
if err != nil || info.IsDir() {
|
|
// Missing files and directories can't be markup files.
|
|
return false
|
|
}
|
|
|
|
// Ensure the base name is expected.
|
|
name := info.Name()
|
|
ext := filepath.Ext(name)
|
|
base := strings.TrimSuffix(name, ext)
|
|
if base != expect {
|
|
return false
|
|
}
|
|
|
|
// Check all supported extensions.
|
|
for _, mext := range encoding.Exts {
|
|
if name == expect+mext {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|