pulumi/pkg/cmd/pulumi/install.go

181 lines
5.7 KiB
Go

// Copyright 2016-2023, 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.
package main
import (
"fmt"
"io"
"os"
"time"
"github.com/opentracing/opentracing-go"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/v3/backend/display"
"github.com/pulumi/pulumi/pkg/v3/engine"
"github.com/pulumi/pulumi/sdk/v3/go/common/diag"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
)
func newInstallCmd() *cobra.Command {
var reinstall bool
var noPlugins, noDependencies bool
cmd := &cobra.Command{
Use: "install",
Args: cmdutil.NoArgs,
Short: "Install packages and plugins for the current program or policy pack.",
Long: "Install packages and plugins for the current program or policy pack.\n" +
"\n" +
"This command is used to manually install packages and plugins required by your program or policy pack.",
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
displayOpts := display.Options{
Color: cmdutil.GetGlobalColorization(),
}
projectPath, err := workspace.DetectProjectPath()
if err != nil || projectPath == "" {
// No project found, check if we are in a policy pack project and install the policy
// pack dependencies if so.
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("getting the working directory: %w", err)
}
policyPackPath, err := workspace.DetectPolicyPackPathFrom(cwd)
if err == nil && policyPackPath != "" {
proj, _, root, err := readPolicyProject(policyPackPath)
if err != nil {
return err
}
return installPolicyPackDependencies(ctx, root, proj)
}
}
// Load the project
proj, root, err := readProject()
if err != nil {
return err
}
span := opentracing.SpanFromContext(ctx)
projinfo := &engine.Projinfo{Proj: proj, Root: root}
pwd, main, pctx, err := engine.ProjectInfoContext(
projinfo,
nil,
cmdutil.Diag(),
cmdutil.Diag(),
false,
span,
nil,
)
if err != nil {
return err
}
defer pctx.Close()
// First make sure the language plugin is present. We need this to load the required resource plugins.
// TODO: we need to think about how best to version this. For now, it always picks the latest.
runtime := proj.Runtime
programInfo := plugin.NewProgramInfo(pctx.Root, pwd, main, runtime.Options())
lang, err := pctx.Host.LanguageRuntime(runtime.Name(), programInfo)
if err != nil {
return fmt.Errorf("load language plugin %s: %w", runtime.Name(), err)
}
if !noDependencies {
if err = lang.InstallDependencies(programInfo); err != nil {
return fmt.Errorf("installing dependencies: %w", err)
}
}
if !noPlugins {
// Compute the set of plugins the current project needs.
installs, err := lang.GetRequiredPlugins(programInfo)
if err != nil {
return err
}
// Now for each kind, name, version pair, download it from the release website, and install it.
for _, install := range installs {
// PluginSpec.String() just returns the name and version, we want the kind too.
label := fmt.Sprintf("%s plugin %s", install.Kind, install)
// If the plugin already exists, don't download it unless --reinstall was passed.
if !reinstall {
if install.Version != nil {
if workspace.HasPlugin(install) {
logging.V(1).Infof("%s skipping install (existing == match)", label)
continue
}
} else {
if has, _ := workspace.HasPluginGTE(install); has {
logging.V(1).Infof("%s skipping install (existing >= match)", label)
continue
}
}
}
pctx.Diag.Infoerrf(diag.Message("", "%s installing"), label)
// If we got here, actually try to do the download.
withProgress := func(stream io.ReadCloser, size int64) io.ReadCloser {
return workspace.ReadCloserProgressBar(stream, size, "Downloading plugin", displayOpts.Color)
}
retry := func(err error, attempt int, limit int, delay time.Duration) {
pctx.Diag.Warningf(
diag.Message("", "Error downloading plugin: %s\nWill retry in %v [%d/%d]"), err, delay, attempt, limit)
}
r, err := workspace.DownloadToFile(install, withProgress, retry)
if err != nil {
return fmt.Errorf("%s downloading from %s: %w", label, install.PluginDownloadURL, err)
}
defer func() {
err := os.Remove(r.Name())
if err != nil {
pctx.Diag.Warningf(
diag.Message("", "Error removing temporary file %s: %s"), r.Name(), err)
}
}()
payload := workspace.TarPlugin(r)
logging.V(1).Infof("%s installing tarball ...", label)
if err = install.InstallWithContext(ctx, payload, reinstall); err != nil {
return fmt.Errorf("installing %s: %w", label, err)
}
}
}
return nil
}),
}
cmd.PersistentFlags().BoolVar(&reinstall,
"reinstall", false, "Reinstall a plugin even if it already exists")
cmd.PersistentFlags().BoolVar(&noPlugins,
"no-plugins", false, "Skip installing plugins")
cmd.PersistentFlags().BoolVar(&noDependencies,
"no-dependencies", false, "Skip installing dependencies")
return cmd
}