pulumi/sdk/go/common/workspace/plugins.go

919 lines
31 KiB
Go
Raw Normal View History

2018-05-22 21:43:36 +02:00
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
package workspace
import (
"bytes"
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
"fmt"
"io"
"io/ioutil"
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
"net/http"
"net/url"
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
"os"
"os/exec"
"path"
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
"time"
"github.com/blang/semver"
"github.com/cheggaaa/pb"
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
"github.com/djherbis/times"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/archive"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/fsutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/httputil"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
"github.com/pulumi/pulumi/sdk/v3/go/common/version"
"github.com/pulumi/pulumi/sdk/v3/nodejs/npm"
"github.com/pulumi/pulumi/sdk/v3/python"
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
)
const (
windowsGOOS = "windows"
)
var (
enableLegacyPluginBehavior = os.Getenv("PULUMI_ENABLE_LEGACY_PLUGIN_SEARCH") != ""
)
// MissingError is returned by functions that attempt to load plugins if a plugin can't be located.
type MissingError struct {
// Info contains information about the plugin that was not found.
Info PluginInfo
}
// NewMissingError allocates a new error indicating the given plugin info was not found.
func NewMissingError(info PluginInfo) error {
return &MissingError{
Info: info,
}
}
func (err *MissingError) Error() string {
if err.Info.Version != nil {
return fmt.Sprintf("no %[1]s plugin '%[2]s-v%[3]s' found in the workspace or on your $PATH, "+
"install the plugin using `pulumi plugin install %[1]s %[2]s v%[3]s`",
err.Info.Kind, err.Info.Name, err.Info.Version)
}
return fmt.Sprintf("no %s plugin '%s' found in the workspace or on your $PATH",
err.Info.Kind, err.Info.String())
}
// PluginInfo provides basic information about a plugin. Each plugin gets installed into a system-wide
// location, by default `~/.pulumi/plugins/<kind>-<name>-<version>/`. A plugin may contain multiple files,
// however the primary loadable executable must be named `pulumi-<kind>-<name>`.
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
type PluginInfo struct {
Name string // the simple name of the plugin.
Path string // the path that a plugin was loaded from.
Kind PluginKind // the kind of the plugin (language, resource, etc).
Version *semver.Version // the plugin's semantic version, if present.
Size int64 // the size of the plugin, in bytes.
InstallTime time.Time // the time the plugin was installed.
LastUsedTime time.Time // the last time the plugin was used.
ServerURL string // an optional server to use when downloading this plugin.
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
PluginDir string // if set, will be used as the root plugin dir instead of ~/.pulumi/plugins.
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// Dir gets the expected plugin directory for this plugin.
func (info PluginInfo) Dir() string {
dir := fmt.Sprintf("%s-%s", info.Kind, info.Name)
if info.Version != nil {
dir = fmt.Sprintf("%s-v%s", dir, info.Version.String())
}
return dir
}
// File gets the expected filename for this plugin.
func (info PluginInfo) File() string {
return info.FilePrefix() + info.FileSuffix()
}
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
// FilePrefix gets the expected default file prefix for the plugin.
func (info PluginInfo) FilePrefix() string {
return fmt.Sprintf("pulumi-%s-%s", info.Kind, info.Name)
}
// FileSuffix returns the suffix for the plugin (if any).
func (info PluginInfo) FileSuffix() string {
if runtime.GOOS == windowsGOOS {
return ".exe"
}
return ""
}
// DirPath returns the directory where this plugin should be installed.
func (info PluginInfo) DirPath() (string, error) {
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
var err error
dir := info.PluginDir
if dir == "" {
dir, err = GetPluginDir()
if err != nil {
return "", err
}
}
return filepath.Join(dir, info.Dir()), nil
}
// LockFilePath returns the full path to the plugin's lock file used during installation
// to prevent concurrent installs.
func (info PluginInfo) LockFilePath() (string, error) {
dir, err := info.DirPath()
if err != nil {
return "", err
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
return fmt.Sprintf("%s.lock", dir), nil
}
// PartialFilePath returns the full path to the plugin's partial file used during installation
// to indicate installation of the plugin hasn't completed yet.
func (info PluginInfo) PartialFilePath() (string, error) {
dir, err := info.DirPath()
if err != nil {
return "", err
}
return fmt.Sprintf("%s.partial", dir), nil
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// FilePath returns the full path where this plugin's primary executable should be installed.
func (info PluginInfo) FilePath() (string, error) {
dir, err := info.DirPath()
if err != nil {
return "", err
}
return filepath.Join(dir, info.File()), nil
}
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
// Delete removes the plugin from the cache. It also deletes any supporting files in the cache, which includes
// any files that contain the same prefix as the plugin itself.
func (info PluginInfo) Delete() error {
dir, err := info.DirPath()
if err != nil {
return err
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
if err := os.RemoveAll(dir); err != nil {
return err
}
// Attempt to delete any leftover .partial or .lock files.
// Don't fail the operation if we can't delete these.
contract.IgnoreError(os.Remove(fmt.Sprintf("%s.partial", dir)))
contract.IgnoreError(os.Remove(fmt.Sprintf("%s.lock", dir)))
return nil
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// SetFileMetadata adds extra metadata from the given file, representing this plugin's directory.
func (info *PluginInfo) SetFileMetadata(path string) error {
// Get the file info.
file, err := os.Stat(path)
if err != nil {
return err
}
// Next, get the size from the directory (or, if there is none, just the file).
size, err := getPluginSize(path)
if err != nil {
return errors.Wrapf(err, "getting plugin dir %s size", path)
}
info.Size = size
// Next get the access times from the plugin binary itself.
tinfo := times.Get(file)
if tinfo.HasBirthTime() {
info.InstallTime = tinfo.BirthTime()
}
info.LastUsedTime = tinfo.AccessTime()
return nil
}
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
// Download fetches an io.ReadCloser for this plugin and also returns the size of the response (if known).
func (info PluginInfo) Download() (io.ReadCloser, int64, error) {
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
// Figure out the OS/ARCH pair for the download URL.
var os string
switch runtime.GOOS {
case "darwin", "linux", "windows":
os = runtime.GOOS
default:
return nil, -1, errors.Errorf("unsupported plugin OS: %s", runtime.GOOS)
}
var arch string
switch runtime.GOARCH {
case "amd64", "arm64":
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
arch = runtime.GOARCH
default:
return nil, -1, errors.Errorf("unsupported plugin architecture: %s", runtime.GOARCH)
}
// If the plugin has a server, associated with it, download from there. Otherwise use the "default" location, which
// is hosted by Pulumi.
serverURL := info.ServerURL
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
if serverURL == "" {
serverURL = "https://get.pulumi.com/releases/plugins"
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
}
serverURL = strings.TrimSuffix(serverURL, "/")
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
Respect logging verbosity as part of pulumi plugin install (#5549) Fixes: #4427 By default, there is no indepth logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 [resource plugin azure-nextgen-0.2.1] installing Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin... done. ``` We can increase the logging verosity get more detailed logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=1 --logtostderr [resource plugin azure-nextgen-0.2.1] installing I1010 19:30:58.472772 7128 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:30:58.704168 7128 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 3s Moving plugin...I1010 19:31:01.874427 7128 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp884796935" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ``` The most verbose logging level will log the request and response headers ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=9 --logtostderr I1010 19:29:46.989150 7089 sink.go:146] defaultSink::Infoerr([resource plugin azure-nextgen-0.2.1] installing) [resource plugin azure-nextgen-0.2.1] installing I1010 19:29:46.989295 7089 plugins.go:591] SelectCompatiblePlugin(..., azure-nextgen): beginning I1010 19:29:46.989300 7089 plugins.go:630] SelectCompatiblePlugin(..., azure-nextgen): failed to find match I1010 19:29:46.989323 7089 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins I1010 19:29:46.989333 7089 plugins.go:208] full plugin download url: https://get.pulumi.com/releases/plugins/pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz I1010 19:29:46.989360 7089 plugins.go:218] plugin install request headers: map[User-Agent:[pulumi-cli/1 (; darwin)]] I1010 19:29:47.242941 7089 plugins.go:225] plugin install response headers: map[Accept-Ranges:[bytes] Age:[370098] Content-Disposition:[attachment; filename=pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz] Content-Length:[18684382] Content-Type:[application/x-gzip] Date:[Tue, 06 Oct 2020 11:41:30 GMT] Etag:["518f2e7efd46fe5b7be9508dc785b9c9-4"] Last-Modified:[Sat, 03 Oct 2020 20:57:45 GMT] Server:[AmazonS3] Via:[1.1 198b7d1bb217783eef010e6636984c9f.cloudfront.net (CloudFront)] X-Amz-Cf-Id:[aaC3Q0WcecspsFbWROLxXnD6iF7sYoAfShQKYiS2xkFKAWlFRBu-1Q==] X-Amz-Cf-Pop:[MAN50-C2] X-Cache:[Hit from cloudfront]] Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:29:47.243189 7089 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin...I1010 19:29:50.031099 7089 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp960784076" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ```
2020-10-13 14:09:29 +02:00
logging.V(1).Infof("%s downloading from %s", info.Name, serverURL)
// URL escape the path value to ensure we have the correct path for S3/CloudFront.
endpoint := fmt.Sprintf("%s/%s",
serverURL,
url.QueryEscape(fmt.Sprintf("pulumi-%s-%s-v%s-%s-%s.tar.gz", info.Kind, info.Name, info.Version, os, arch)))
Respect logging verbosity as part of pulumi plugin install (#5549) Fixes: #4427 By default, there is no indepth logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 [resource plugin azure-nextgen-0.2.1] installing Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin... done. ``` We can increase the logging verosity get more detailed logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=1 --logtostderr [resource plugin azure-nextgen-0.2.1] installing I1010 19:30:58.472772 7128 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:30:58.704168 7128 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 3s Moving plugin...I1010 19:31:01.874427 7128 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp884796935" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ``` The most verbose logging level will log the request and response headers ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=9 --logtostderr I1010 19:29:46.989150 7089 sink.go:146] defaultSink::Infoerr([resource plugin azure-nextgen-0.2.1] installing) [resource plugin azure-nextgen-0.2.1] installing I1010 19:29:46.989295 7089 plugins.go:591] SelectCompatiblePlugin(..., azure-nextgen): beginning I1010 19:29:46.989300 7089 plugins.go:630] SelectCompatiblePlugin(..., azure-nextgen): failed to find match I1010 19:29:46.989323 7089 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins I1010 19:29:46.989333 7089 plugins.go:208] full plugin download url: https://get.pulumi.com/releases/plugins/pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz I1010 19:29:46.989360 7089 plugins.go:218] plugin install request headers: map[User-Agent:[pulumi-cli/1 (; darwin)]] I1010 19:29:47.242941 7089 plugins.go:225] plugin install response headers: map[Accept-Ranges:[bytes] Age:[370098] Content-Disposition:[attachment; filename=pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz] Content-Length:[18684382] Content-Type:[application/x-gzip] Date:[Tue, 06 Oct 2020 11:41:30 GMT] Etag:["518f2e7efd46fe5b7be9508dc785b9c9-4"] Last-Modified:[Sat, 03 Oct 2020 20:57:45 GMT] Server:[AmazonS3] Via:[1.1 198b7d1bb217783eef010e6636984c9f.cloudfront.net (CloudFront)] X-Amz-Cf-Id:[aaC3Q0WcecspsFbWROLxXnD6iF7sYoAfShQKYiS2xkFKAWlFRBu-1Q==] X-Amz-Cf-Pop:[MAN50-C2] X-Cache:[Hit from cloudfront]] Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:29:47.243189 7089 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin...I1010 19:29:50.031099 7089 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp960784076" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ```
2020-10-13 14:09:29 +02:00
logging.V(9).Infof("full plugin download url: %s", endpoint)
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return nil, -1, err
}
userAgent := fmt.Sprintf("pulumi-cli/1 (%s; %s)", version.Version, runtime.GOOS)
req.Header.Set("User-Agent", userAgent)
Respect logging verbosity as part of pulumi plugin install (#5549) Fixes: #4427 By default, there is no indepth logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 [resource plugin azure-nextgen-0.2.1] installing Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin... done. ``` We can increase the logging verosity get more detailed logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=1 --logtostderr [resource plugin azure-nextgen-0.2.1] installing I1010 19:30:58.472772 7128 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:30:58.704168 7128 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 3s Moving plugin...I1010 19:31:01.874427 7128 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp884796935" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ``` The most verbose logging level will log the request and response headers ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=9 --logtostderr I1010 19:29:46.989150 7089 sink.go:146] defaultSink::Infoerr([resource plugin azure-nextgen-0.2.1] installing) [resource plugin azure-nextgen-0.2.1] installing I1010 19:29:46.989295 7089 plugins.go:591] SelectCompatiblePlugin(..., azure-nextgen): beginning I1010 19:29:46.989300 7089 plugins.go:630] SelectCompatiblePlugin(..., azure-nextgen): failed to find match I1010 19:29:46.989323 7089 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins I1010 19:29:46.989333 7089 plugins.go:208] full plugin download url: https://get.pulumi.com/releases/plugins/pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz I1010 19:29:46.989360 7089 plugins.go:218] plugin install request headers: map[User-Agent:[pulumi-cli/1 (; darwin)]] I1010 19:29:47.242941 7089 plugins.go:225] plugin install response headers: map[Accept-Ranges:[bytes] Age:[370098] Content-Disposition:[attachment; filename=pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz] Content-Length:[18684382] Content-Type:[application/x-gzip] Date:[Tue, 06 Oct 2020 11:41:30 GMT] Etag:["518f2e7efd46fe5b7be9508dc785b9c9-4"] Last-Modified:[Sat, 03 Oct 2020 20:57:45 GMT] Server:[AmazonS3] Via:[1.1 198b7d1bb217783eef010e6636984c9f.cloudfront.net (CloudFront)] X-Amz-Cf-Id:[aaC3Q0WcecspsFbWROLxXnD6iF7sYoAfShQKYiS2xkFKAWlFRBu-1Q==] X-Amz-Cf-Pop:[MAN50-C2] X-Cache:[Hit from cloudfront]] Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:29:47.243189 7089 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin...I1010 19:29:50.031099 7089 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp960784076" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ```
2020-10-13 14:09:29 +02:00
logging.V(9).Infof("plugin install request headers: %v", req.Header)
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
resp, err := httputil.DoWithRetry(req, http.DefaultClient)
if err != nil {
return nil, -1, err
}
Respect logging verbosity as part of pulumi plugin install (#5549) Fixes: #4427 By default, there is no indepth logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 [resource plugin azure-nextgen-0.2.1] installing Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin... done. ``` We can increase the logging verosity get more detailed logging: ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=1 --logtostderr [resource plugin azure-nextgen-0.2.1] installing I1010 19:30:58.472772 7128 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:30:58.704168 7128 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 3s Moving plugin...I1010 19:31:01.874427 7128 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp884796935" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ``` The most verbose logging level will log the request and response headers ``` pulumi plugin install resource azure-nextgen v0.2.1 -v=9 --logtostderr I1010 19:29:46.989150 7089 sink.go:146] defaultSink::Infoerr([resource plugin azure-nextgen-0.2.1] installing) [resource plugin azure-nextgen-0.2.1] installing I1010 19:29:46.989295 7089 plugins.go:591] SelectCompatiblePlugin(..., azure-nextgen): beginning I1010 19:29:46.989300 7089 plugins.go:630] SelectCompatiblePlugin(..., azure-nextgen): failed to find match I1010 19:29:46.989323 7089 plugins.go:201] azure-nextgen downloading from https://get.pulumi.com/releases/plugins I1010 19:29:46.989333 7089 plugins.go:208] full plugin download url: https://get.pulumi.com/releases/plugins/pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz I1010 19:29:46.989360 7089 plugins.go:218] plugin install request headers: map[User-Agent:[pulumi-cli/1 (; darwin)]] I1010 19:29:47.242941 7089 plugins.go:225] plugin install response headers: map[Accept-Ranges:[bytes] Age:[370098] Content-Disposition:[attachment; filename=pulumi-resource-azure-nextgen-v0.2.1-darwin-amd64.tar.gz] Content-Length:[18684382] Content-Type:[application/x-gzip] Date:[Tue, 06 Oct 2020 11:41:30 GMT] Etag:["518f2e7efd46fe5b7be9508dc785b9c9-4"] Last-Modified:[Sat, 03 Oct 2020 20:57:45 GMT] Server:[AmazonS3] Via:[1.1 198b7d1bb217783eef010e6636984c9f.cloudfront.net (CloudFront)] X-Amz-Cf-Id:[aaC3Q0WcecspsFbWROLxXnD6iF7sYoAfShQKYiS2xkFKAWlFRBu-1Q==] X-Amz-Cf-Pop:[MAN50-C2] X-Cache:[Hit from cloudfront]] Downloading plugin: 0 B / 17.82 MiB [----------------------------------] 0.00%I1010 19:29:47.243189 7089 plugin_install.go:136] [resource plugin azure-nextgen-0.2.1] installing tarball ... Downloading plugin: 17.82 MiB / 17.82 MiB [=========================] 100.00% 2s Moving plugin...I1010 19:29:50.031099 7089 plugins.go:304] moving plugin from "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1.tmp960784076" to "/Users/myuser/.pulumi/plugins/resource-azure-nextgen-v0.2.1" done. ```
2020-10-13 14:09:29 +02:00
logging.V(9).Infof("plugin install response headers: %v", resp.Header)
Add `--server` to `pulumi plugin install` Previously, when the CLI wanted to install a plugin, it used a special method, `DownloadPlugin` on the `httpstate` backend to actually fetch the tarball that had the plugin. The reason for this is largely tied to history, at one point during a closed beta, we required presenting an API key to download plugins (as a way to enforce folks outside the beta could not download them) and because of that it was natural to bake that functionality into the part of the code that interfaced with the rest of the API from the Pulumi Service. The downside here is that it means we need to host all the plugins on `api.pulumi.com` which prevents community folks from being able to easily write resource providers, since they have to manually manage the process of downloading a provider to a machine and getting it on the `$PATH` or putting it in the plugin cache. To make this easier, we add a `--server` argument you can pass to `pulumi plugin install` to control the URL that it attempts to fetch the tarball from. We still have perscriptive guidence on how the tarball must be named (`pulumi-[<type>]-[<provider-name>]-vX.Y.Z.tar.gz`) but the base URL can now be configured. Folks publishing packages can use install scripts to run `pulumi plugin install` passing a custom `--server` argument, if needed. There are two improvements we can make to provide a nicer end to end story here: - We can augment the GetRequiredPlugins method on the language provider to also return information about an optional server to use when downloading the provider. - We can pass information about a server to download plugins from as part of a resource registration or creation of a first class provider. These help out in cases where for one reason or another where `pulumi plugin install` doesn't get run before an update takes place and would allow us to either do the right thing ahead of time or provide better error messages with the correct `--server` argument. But, for now, this unblocks a majority of the cases we care about and provides a path forward for folks that want to develop and host their own resource providers.
2019-05-30 22:56:55 +02:00
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return nil, -1, errors.Errorf("%d HTTP error fetching plugin from %s", resp.StatusCode, endpoint)
}
return resp.Body, resp.ContentLength, nil
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// installLock acquires a file lock used to prevent concurrent installs.
func (info PluginInfo) installLock() (unlock func(), err error) {
finalDir, err := info.DirPath()
if err != nil {
return nil, err
}
lockFilePath := fmt.Sprintf("%s.lock", finalDir)
if err := os.MkdirAll(filepath.Dir(lockFilePath), 0700); err != nil {
return nil, errors.Wrap(err, "creating plugin root")
}
mutex := fsutil.NewFileMutex(lockFilePath)
if err := mutex.Lock(); err != nil {
return nil, err
}
return func() {
contract.IgnoreError(mutex.Unlock())
}, nil
}
// Install installs a plugin's tarball into the cache. It validates that plugin names are in the expected format.
// Previous versions of Pulumi extracted the tarball to a temp directory first, and then renamed the temp directory
// to the final directory. The rename operation fails often enough on Windows due to aggressive virus scanners opening
// files in the temp directory. To address this, we now extract the tarball directly into the final directory, and use
// file locks to prevent concurrent installs.
// Each plugin has its own file lock, with the same name as the plugin directory, with a `.lock` suffix.
// During installation an empty file with a `.partial` suffix is created, indicating that installation is in-progress.
// The `.partial` file is deleted when installation is complete, indicating that the plugin has finished installing.
// If a failure occurs during installation, the `.partial` file will remain, indicating the plugin wasn't fully
// installed. The next time the plugin is installed, the old installation directory will be removed and replaced with
// a fresh install.
func (info PluginInfo) Install(tgz io.ReadCloser) error {
defer contract.IgnoreClose(tgz)
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// Fetch the directory into which we will expand this tarball.
finalDir, err := info.DirPath()
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
if err != nil {
return err
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// Create a file lock file at <pluginsdir>/<kind>-<name>-<version>.lock.
unlock, err := info.installLock()
if err != nil {
return err
}
defer unlock()
// Cleanup any temp dirs from failed installations of this plugin from previous versions of Pulumi.
if err := cleanupTempDirs(finalDir); err != nil {
// We don't want to fail the installation if there was an error cleaning up these old temp dirs.
// Instead, log the error and continue on.
logging.V(5).Infof("Install: Error cleaning up temp dirs: %s", err.Error())
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// Get the partial file path (e.g. <pluginsdir>/<kind>-<name>-<version>.partial).
partialFilePath, err := info.PartialFilePath()
if err != nil {
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
return err
}
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// Check whether the directory exists while we were waiting on the lock.
_, finalDirStatErr := os.Stat(finalDir)
if finalDirStatErr == nil {
_, partialFileStatErr := os.Stat(partialFilePath)
if partialFileStatErr != nil {
if os.IsNotExist(partialFileStatErr) {
// finalDir exists and there's no partial file, so the plugin is already installed.
return nil
}
return partialFileStatErr
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// The partial file exists, meaning a previous attempt at installing the plugin failed.
// Delete finalDir so we can try installing again. There's no need to delete the partial
// file since we'd just be recreating it again below anyway.
if err := os.RemoveAll(finalDir); err != nil {
return err
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
} else if !os.IsNotExist(finalDirStatErr) {
return finalDirStatErr
}
// Create an empty partial file to indicate installation is in-progress.
if err := ioutil.WriteFile(partialFilePath, nil, 0600); err != nil {
return err
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// Create the final directory.
if err := os.MkdirAll(finalDir, 0700); err != nil {
return err
}
// Uncompress the plugin.
if err := archive.ExtractTGZ(tgz, finalDir); err != nil {
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
return err
}
// Even though we deferred closing the tarball at the beginning of this function, go ahead and explicitly close
// it now since we're finished extracting it, to prevent subsequent output from being displayed oddly with
// the progress bar.
contract.IgnoreClose(tgz)
// Install dependencies, if needed.
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
proj, err := LoadPluginProject(filepath.Join(finalDir, "PulumiPlugin.yaml"))
if err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "loading PulumiPlugin.yaml")
}
if proj != nil {
runtime := strings.ToLower(proj.Runtime.Name())
// For now, we only do this for Node.js and Python. For Go, the expectation is the binary is
// already built. For .NET, similarly, a single self-contained binary could be used, but
// otherwise `dotnet run` will implicitly run `dotnet restore`.
// TODO[pulumi/pulumi#1334]: move to the language plugins so we don't have to hard code here.
switch runtime {
case "nodejs":
var b bytes.Buffer
if _, err := npm.Install(finalDir, &b, &b); err != nil {
os.Stderr.Write(b.Bytes())
return errors.Wrap(err, "installing plugin dependencies")
}
case "python":
if err := python.InstallDependencies(finalDir, "venv", false /*showOutput*/); err != nil {
return errors.Wrap(err, "installing plugin dependencies")
}
}
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// Installation is complete. Remove the partial file.
return os.Remove(partialFilePath)
}
// cleanupTempDirs cleans up leftover temp dirs from failed installs with previous versions of Pulumi.
func cleanupTempDirs(finalDir string) error {
dir := filepath.Dir(finalDir)
infos, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
for _, info := range infos {
// Temp dirs have a suffix of `.tmpXXXXXX` (where `XXXXXX`) is a random number,
// from ioutil.TempFile.
if info.IsDir() && installingPluginRegexp.MatchString(info.Name()) {
path := filepath.Join(dir, info.Name())
if err := os.RemoveAll(path); err != nil {
return errors.Wrapf(err, "cleaning up temp dir %s", path)
}
}
}
return nil
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
func (info PluginInfo) String() string {
var version string
if v := info.Version; v != nil {
version = fmt.Sprintf("-%s", v)
}
return info.Name + version
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// PluginKind represents a kind of a plugin that may be dynamically loaded and used by Pulumi.
type PluginKind string
const (
// AnalyzerPlugin is a plugin that can be used as a resource analyzer.
AnalyzerPlugin PluginKind = "analyzer"
// LanguagePlugin is a plugin that can be used as a language host.
LanguagePlugin PluginKind = "language"
// ResourcePlugin is a plugin that can be used as a resource provider for custom CRUD operations.
ResourcePlugin PluginKind = "resource"
)
// IsPluginKind returns true if k is a valid plugin kind, and false otherwise.
func IsPluginKind(k string) bool {
switch PluginKind(k) {
case AnalyzerPlugin, LanguagePlugin, ResourcePlugin:
return true
default:
return false
}
}
// HasPlugin returns true if the given plugin exists.
func HasPlugin(plug PluginInfo) bool {
dir, err := plug.DirPath()
if err == nil {
_, err := os.Stat(dir)
if err == nil {
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
partialFilePath, err := plug.PartialFilePath()
if err == nil {
if _, err := os.Stat(partialFilePath); os.IsNotExist(err) {
return true
}
}
}
}
return false
}
// HasPluginGTE returns true if the given plugin exists at the given version number or greater.
func HasPluginGTE(plug PluginInfo) (bool, error) {
// If an exact match, return true right away.
if HasPlugin(plug) {
return true, nil
}
// Otherwise, load up the list of plugins and find one with the same name/type and >= version.
plugs, err := GetPlugins()
if err != nil {
return false, err
}
// If we're not doing the legacy plugin behavior and we've been asked for a specific version, do the same plugin
// search that we'd do at runtime. This ensures that `pulumi plugin install` works the same way that the runtime
// loader does, to minimize confusion when a user has to install new plugins.
if !enableLegacyPluginBehavior && plug.Version != nil {
requestedVersion := semver.MustParseRange(plug.Version.String())
_, err := SelectCompatiblePlugin(plugs, plug.Kind, plug.Name, requestedVersion)
return err == nil, err
}
for _, p := range plugs {
if p.Name == plug.Name &&
p.Kind == plug.Kind &&
(p.Version != nil && plug.Version != nil && p.Version.GTE(*plug.Version)) {
return true, nil
}
}
return false, nil
}
2020-02-26 18:45:39 +01:00
// GetPolicyDir returns the directory in which an organization's Policy Packs on the current machine are managed.
func GetPolicyDir(orgName string) (string, error) {
return GetPulumiPath(PolicyDir, orgName)
}
// GetPolicyPath finds a PolicyPack by its name version, as well as a bool marked true if the path
// already exists and is a directory.
func GetPolicyPath(orgName, name, version string) (string, bool, error) {
policiesDir, err := GetPolicyDir(orgName)
if err != nil {
return "", false, err
}
policyPackPath := path.Join(policiesDir, fmt.Sprintf("pulumi-analyzer-%s-v%s", name, version))
file, err := os.Stat(policyPackPath)
if err == nil && file.IsDir() {
// PolicyPack exists. Return.
return policyPackPath, true, nil
} else if err != nil && !os.IsNotExist(err) {
// Error trying to inspect PolicyPack FS entry. Return error.
return "", false, err
}
// Not found. Return empty path.
return policyPackPath, false, nil
}
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
// GetPluginDir returns the directory in which plugins on the current machine are managed.
func GetPluginDir() (string, error) {
return GetPulumiPath(PluginDir)
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// GetPlugins returns a list of installed plugins without size info and last accessed metadata.
// Plugin size requires recursively traversing the plugin directory, which can be extremely
// expensive with the introduction of nodejs multilang components that have
// deeply nested node_modules folders.
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
func GetPlugins() ([]PluginInfo, error) {
// To get the list of plugins, simply scan the directory in the usual place.
dir, err := GetPluginDir()
if err != nil {
return nil, err
}
return getPlugins(dir, true /* skipMetadata */)
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
}
// GetPluginsWithMetadata returns a list of installed plugins with metadata about size,
// and last access (POOR RUNTIME PERF). Plugin size requires recursively traversing the
// plugin directory, which can be extremely expensive with the introduction of
// nodejs multilang components that have deeply nested node_modules folders.
func GetPluginsWithMetadata() ([]PluginInfo, error) {
// To get the list of plugins, simply scan the directory in the usual place.
dir, err := GetPluginDir()
if err != nil {
return nil, err
}
return getPlugins(dir, false /* skipMetadata */)
}
func getPlugins(dir string, skipMetadata bool) ([]PluginInfo, error) {
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
files, err := ioutil.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
// Now read the file infos and create the plugin infos.
var plugins []PluginInfo
for _, file := range files {
// Skip anything that doesn't look like a plugin.
if kind, name, version, ok := tryPlugin(file); ok {
plugin := PluginInfo{
Name: name,
Kind: kind,
Version: &version,
}
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
path := filepath.Join(dir, file.Name())
if _, err := os.Stat(fmt.Sprintf("%s.partial", path)); err == nil {
// Skip it if the partial file exists, meaning the plugin is not fully installed.
continue
} else if !os.IsNotExist(err) {
return nil, err
}
// computing plugin sizes can be very expensive (nested node_modules)
if !skipMetadata {
if err = plugin.SetFileMetadata(path); err != nil {
return nil, err
}
}
plugins = append(plugins, plugin)
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
}
return plugins, nil
}
// GetPluginPath finds a plugin's path by its kind, name, and optional version. It will match the latest version that
// is >= the version specified. If no version is supplied, the latest plugin for that given kind/name pair is loaded,
// using standard semver sorting rules. A plugin may be overridden entirely by placing it on your $PATH, though it is
// possible to opt out of this behavior by setting PULUMI_IGNORE_AMBIENT_PLUGINS to any non-empty value.
func GetPluginPath(kind PluginKind, name string, version *semver.Version) (string, string, error) {
var filename string
// If we have a version of the plugin on its $PATH, use it, unless we have opted out of this behavior explicitly.
// This supports development scenarios.
if _, isFound := os.LookupEnv("PULUMI_IGNORE_AMBIENT_PLUGINS"); !isFound {
filename = (&PluginInfo{Kind: kind, Name: name, Version: version}).FilePrefix()
if path, err := exec.LookPath(filename); err == nil {
logging.V(6).Infof("GetPluginPath(%s, %s, %v): found on $PATH %s", kind, name, version, path)
return "", path, nil
}
}
// At some point in the future, language plugins will be located in the plugin cache, just like regular plugins
// (see pulumi/pulumi#956 for some of the reasons why this isn't the case today). For now, they ship next to the
// `pulumi` binary. While we encourage this folder to be on the $PATH (and so the check above would have found
// the language plugin) it's possible someone is running `pulumi` with an explicit path on the command line or
// has done symlink magic such that `pulumi` is on the path, but the language plugins are not. So, if possible,
// look next to the instance of `pulumi` that is running to find this language plugin.
if kind == LanguagePlugin {
exePath, exeErr := os.Executable()
if exeErr == nil {
fullPath, fullErr := filepath.EvalSymlinks(exePath)
if fullErr == nil {
for _, ext := range getCandidateExtensions() {
candidate := filepath.Join(filepath.Dir(fullPath), filename+ext)
// Let's see if the file is executable. On Windows, os.Stat() returns a mode of "-rw-rw-rw" so on
// on windows we just trust the fact that the .exe can actually be launched.
if stat, err := os.Stat(candidate); err == nil &&
(stat.Mode()&0100 != 0 || runtime.GOOS == windowsGOOS) {
logging.V(6).Infof("GetPluginPath(%s, %s, %v): found next to current executable %s",
kind, name, version, candidate)
return "", candidate, nil
}
}
}
}
}
// Otherwise, check the plugin cache.
plugins, err := GetPlugins()
if err != nil {
return "", "", errors.Wrapf(err, "loading plugin list")
}
var match *PluginInfo
if !enableLegacyPluginBehavior && version != nil {
logging.V(6).Infof("GetPluginPath(%s, %s, %s): enabling new plugin behavior", kind, name, version)
candidate, err := SelectCompatiblePlugin(plugins, kind, name, semver.MustParseRange(version.String()))
if err != nil {
return "", "", NewMissingError(PluginInfo{
Name: name,
Kind: kind,
Version: version,
})
}
match = &candidate
} else {
for _, cur := range plugins {
// Since the value of cur changes as we iterate, we can't save a pointer to it. So let's have a local that
// we can take a pointer to if this plugin is the best match yet.
plugin := cur
if plugin.Kind == kind && plugin.Name == name {
// Always pick the most recent version of the plugin available. Even if this is an exact match, we
// keep on searching just in case there's a newer version available.
var m *PluginInfo
if match == nil && version == nil {
m = &plugin // no existing match, no version spec, take it.
} else if match != nil &&
(match.Version == nil || (plugin.Version != nil && plugin.Version.GT(*match.Version))) {
m = &plugin // existing match, but this plugin is newer, prefer it.
} else if version != nil && plugin.Version != nil && plugin.Version.GTE(*version) {
m = &plugin // this plugin is >= the version being requested, use it.
}
if m != nil {
match = m
logging.V(6).Infof("GetPluginPath(%s, %s, %s): found candidate (#%s)",
kind, name, version, match.Version)
}
}
}
}
if match != nil {
matchDir, err := match.DirPath()
if err != nil {
return "", "", err
}
matchPath, err := match.FilePath()
if err != nil {
return "", "", err
}
logging.V(6).Infof("GetPluginPath(%s, %s, %v): found in cache at %s", kind, name, version, matchPath)
return matchDir, matchPath, nil
}
return "", "", nil
}
// SortedPluginInfo is a wrapper around PluginInfo that allows for sorting by version.
type SortedPluginInfo []PluginInfo
func (sp SortedPluginInfo) Len() int { return len(sp) }
func (sp SortedPluginInfo) Less(i, j int) bool {
iVersion := sp[i].Version
jVersion := sp[j].Version
switch {
case iVersion == nil && jVersion == nil:
return false
case iVersion == nil:
return true
case jVersion == nil:
return false
default:
return iVersion.LT(*jVersion)
}
}
func (sp SortedPluginInfo) Swap(i, j int) { sp[i], sp[j] = sp[j], sp[i] }
// SelectCompatiblePlugin selects a plugin from the list of plugins with the given kind and name that sastisfies the
// requested semver range. It returns the highest version plugin that satisfies the requested constraints, or an error
// if no such plugin could be found.
//
// If there exist plugins in the plugin list that don't have a version, SelectCompatiblePlugin will select them if there
// are no other compatible plugins available.
func SelectCompatiblePlugin(
plugins []PluginInfo, kind PluginKind, name string, requested semver.Range) (PluginInfo, error) {
logging.V(7).Infof("SelectCompatiblePlugin(..., %s): beginning", name)
var bestMatch PluginInfo
var hasMatch bool
// Before iterating over the list of plugins, sort the list of plugins by version in ascending order. This ensures
// that we can do a single pass over the plugin list, from lowest version to greatest version, and be confident that
// the best match that we find at the end is the greatest possible compatible version for the requested plugin.
//
// Plugins without versions are treated as having the lowest version. Ties between plugins without versions are
// resolved arbitrarily.
sort.Sort(SortedPluginInfo(plugins))
for _, plugin := range plugins {
switch {
case plugin.Kind != kind || plugin.Name != name:
// Not the plugin we're looking for.
case !hasMatch && plugin.Version == nil:
// This is the plugin we're looking for, but it doesn't have a version. We haven't seen anything better yet,
// so take it.
logging.V(7).Infof(
"SelectCompatiblePlugin(..., %s): best plugin %s: no version and no other candidates",
name, plugin.String())
hasMatch = true
bestMatch = plugin
case plugin.Version == nil:
// This is a rare case - we've already seen a version-less plugin and we're seeing another here. Ignore this
// one and defer to the one we previously selected.
logging.V(7).Infof("SelectCompatiblePlugin(..., %s): skipping plugin %s: no version", name, plugin.String())
case requested(*plugin.Version):
// This plugin is compatible with the requested semver range. Save it as the best match and continue.
logging.V(7).Infof("SelectCompatiblePlugin(..., %s): best plugin %s: semver match", name, plugin.String())
hasMatch = true
bestMatch = plugin
default:
logging.V(7).Infof(
"SelectCompatiblePlugin(..., %s): skipping plugin %s: semver mismatch", name, plugin.String())
}
}
if !hasMatch {
logging.V(7).Infof("SelectCompatiblePlugin(..., %s): failed to find match", name)
return PluginInfo{}, errors.New("failed to locate compatible plugin")
}
logging.V(7).Infof("SelectCompatiblePlugin(..., %s): selecting plugin '%s': best match ", name, bestMatch.String())
return bestMatch, nil
}
// ReadCloserProgressBar displays a progress bar for the given closer and returns a wrapper closer to manipulate it.
2019-08-21 20:47:58 +02:00
func ReadCloserProgressBar(
closer io.ReadCloser, size int64, message string, colorization colors.Colorization) io.ReadCloser {
if size == -1 {
return closer
}
// If we know the length of the download, show a progress bar.
bar := pb.New(int(size))
bar.Output = os.Stderr
bar.Prefix(colorization.Colorize(colors.SpecUnimportant + message + ":"))
bar.Postfix(colorization.Colorize(colors.Reset))
bar.SetMaxWidth(80)
bar.SetUnits(pb.U_BYTES)
bar.Start()
return &barCloser{
bar: bar,
readCloser: bar.NewProxyReader(closer),
}
}
// getCandidateExtensions returns a set of file extensions (including the dot seprator) which should be used when
// probing for an executable file.
func getCandidateExtensions() []string {
if runtime.GOOS == windowsGOOS {
return []string{".exe", ".cmd"}
}
return []string{""}
}
// pluginRegexp matches plugin directory names: pulumi-KIND-NAME-VERSION.
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
var pluginRegexp = regexp.MustCompile(
"^(?P<Kind>[a-z]+)-" + // KIND
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
"(?P<Name>[a-zA-Z0-9-]*[a-zA-Z0-9])-" + // NAME
"v(?P<Version>.*)$") // VERSION
Fix plugin install failures on Windows (#5759) When installing a plugin, previous versions of Pulumi extracted the plugin tarball to a temp directory and then renamed the temp directory to the final plugin directory. This was done to prevent concurrent installs: if a process fails to rename the temp dir because the final dir already exists, it means another process already installed the plugin. Unfortunately, on Windows the rename operation often fails due to aggressive virus scanners opening files in the temp dir. In order to provide reliable plugin installs on Windows, we now extract the tarball directly into the final directory, and use file locks to prevent concurrent installs from toppling over one another. During install, a lock file is created in the plugin cache directory with the same name as the plugin's final directory but suffixed with `.lock`. The process that obtains the lock is responsible for extracting the tarball. Before it does that, it cleans up any previous temp directories of failed installs of previous versions of Pulumi. Then it creates an empty `.partial` file next to the `.lock` file. The `.partial` file indicates an installation is in-progress. The `.partial` file is deleted when installation is complete, indicating the plugin was successfully installed. If a failure occurs during installation, the `.partial` file will remain indicating the plugin wasn't fully installed. The next time the plugin is installed, the old installation directory will be removed and replaced with a fresh install. This is the same approach Go uses for installing modules in its module cache.
2020-11-16 18:44:29 +01:00
// installingPluginRegexp matches the name of temporary folders. Previous versions of Pulumi first extracted
// plugins to a temporary folder with a suffix of `.tmpXXXXXX` (where `XXXXXX`) is a random number, from
// ioutil.TempFile. We should ignore these folders.
var installingPluginRegexp = regexp.MustCompile(`\.tmp[0-9]+$`)
// tryPlugin returns true if a file is a plugin, and extracts information about it.
func tryPlugin(file os.FileInfo) (PluginKind, string, semver.Version, bool) {
// Only directories contain plugins.
if !file.IsDir() {
logging.V(11).Infof("skipping file in plugin directory: %s", file.Name())
return "", "", semver.Version{}, false
}
// Ignore plugins which are being installed
if installingPluginRegexp.MatchString(file.Name()) {
logging.V(11).Infof("skipping plugin %s which is being installed", file.Name())
return "", "", semver.Version{}, false
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// Filenames must match the plugin regexp.
match := pluginRegexp.FindStringSubmatch(file.Name())
if len(match) != len(pluginRegexp.SubexpNames()) {
logging.V(11).Infof("skipping plugin %s with missing capture groups: expect=%d, actual=%d",
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
file.Name(), len(pluginRegexp.SubexpNames()), len(match))
return "", "", semver.Version{}, false
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
var kind PluginKind
var name string
var version *semver.Version
for i, group := range pluginRegexp.SubexpNames() {
v := match[i]
switch group {
case "Kind":
// Skip invalid kinds.
if IsPluginKind(v) {
kind = PluginKind(v)
} else {
logging.V(11).Infof("skipping invalid plugin kind: %s", v)
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
case "Name":
name = v
case "Version":
// Skip invalid versions.
ver, err := semver.ParseTolerant(v)
if err == nil {
version = &ver
} else {
logging.V(11).Infof("skipping invalid plugin version: %s", v)
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
}
}
// If anything was missing or invalid, skip this plugin.
if kind == "" || name == "" || version == nil {
logging.V(11).Infof("skipping plugin with missing information: kind=%s, name=%s, version=%v",
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
kind, name, version)
return "", "", semver.Version{}, false
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
return kind, name, *version, true
Implement basic plugin management 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.
2018-02-04 19:51:29 +01:00
}
// getPluginSize recursively computes how much space is devoted to a given plugin.
func getPluginSize(path string) (int64, error) {
file, err := os.Stat(path)
if err != nil {
return 0, nil
}
size := int64(0)
if file.IsDir() {
subs, err := ioutil.ReadDir(path)
if err != nil {
return 0, err
}
for _, child := range subs {
add, err := getPluginSize(filepath.Join(path, child.Name()))
if err != nil {
return 0, err
}
size += add
}
} else {
size += file.Size()
}
return size, nil
}
type barCloser struct {
bar *pb.ProgressBar
readCloser io.ReadCloser
}
func (bc *barCloser) Read(dest []byte) (int, error) {
return bc.readCloser.Read(dest)
}
func (bc *barCloser) Close() error {
bc.bar.Finish()
return bc.readCloser.Close()
}