2018-05-22 19:43:36 +00: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.
|
2017-08-30 01:24:12 +00:00
|
|
|
|
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
2022-10-04 08:58:01 +00:00
|
|
|
"context"
|
2024-01-25 23:28:58 +00:00
|
|
|
"fmt"
|
2017-08-30 01:24:12 +00:00
|
|
|
"io"
|
2024-06-28 23:21:55 +00:00
|
|
|
"os/exec"
|
2024-01-25 23:28:58 +00:00
|
|
|
"path/filepath"
|
2024-06-28 23:21:55 +00:00
|
|
|
"sort"
|
2024-06-17 17:10:55 +00:00
|
|
|
"strconv"
|
2017-08-31 21:31:33 +00:00
|
|
|
|
2022-10-17 14:21:11 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2023-10-20 10:44:16 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
2024-01-25 23:28:58 +00:00
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
|
|
|
structpb "google.golang.org/protobuf/types/known/structpb"
|
2017-08-30 01:24:12 +00:00
|
|
|
)
|
|
|
|
|
2024-01-25 23:28:58 +00:00
|
|
|
// ProgramInfo contains minimal information about the program to be run.
|
|
|
|
type ProgramInfo struct {
|
|
|
|
root string
|
|
|
|
program string
|
|
|
|
entryPoint string
|
|
|
|
options map[string]any
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewProgramInfo(rootDirectory, programDirectory, entryPoint string, options map[string]any) ProgramInfo {
|
|
|
|
isFileName := func(path string) bool {
|
|
|
|
return filepath.Base(path) == path
|
|
|
|
}
|
|
|
|
|
2024-02-22 11:43:18 +00:00
|
|
|
if !filepath.IsAbs(rootDirectory) {
|
2024-01-25 23:28:58 +00:00
|
|
|
panic(fmt.Sprintf("rootDirectory '%s' is not a valid path when creating ProgramInfo", rootDirectory))
|
|
|
|
}
|
|
|
|
|
2024-02-22 11:43:18 +00:00
|
|
|
if !filepath.IsAbs(programDirectory) {
|
2024-01-25 23:28:58 +00:00
|
|
|
panic(fmt.Sprintf("programDirectory '%s' is not a valid path when creating ProgramInfo", programDirectory))
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isFileName(entryPoint) && entryPoint != "." {
|
|
|
|
panic(fmt.Sprintf("entryPoint '%s' was not a valid file name when creating ProgramInfo", entryPoint))
|
|
|
|
}
|
|
|
|
|
|
|
|
return ProgramInfo{
|
|
|
|
root: rootDirectory,
|
|
|
|
program: programDirectory,
|
|
|
|
entryPoint: entryPoint,
|
|
|
|
options: options,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The programs root directory, i.e. where the Pulumi.yaml file is.
|
|
|
|
func (info ProgramInfo) RootDirectory() string {
|
|
|
|
return info.root
|
|
|
|
}
|
|
|
|
|
|
|
|
// The programs directory, generally the same as or a subdirectory of the root directory.
|
|
|
|
func (info ProgramInfo) ProgramDirectory() string {
|
|
|
|
return info.program
|
|
|
|
}
|
|
|
|
|
|
|
|
// The programs main entrypoint, either a file path relative to the program directory or "." for the program directory.
|
|
|
|
func (info ProgramInfo) EntryPoint() string {
|
|
|
|
return info.entryPoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runtime plugin options for the program
|
|
|
|
func (info ProgramInfo) Options() map[string]any {
|
|
|
|
return info.options
|
|
|
|
}
|
|
|
|
|
|
|
|
func (info ProgramInfo) String() string {
|
|
|
|
return fmt.Sprintf("root=%s, program=%s, entryPoint=%s", info.root, info.program, info.entryPoint)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (info ProgramInfo) Marshal() (*pulumirpc.ProgramInfo, error) {
|
|
|
|
opts, err := structpb.NewStruct(info.options)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to marshal options: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pulumirpc.ProgramInfo{
|
|
|
|
RootDirectory: info.root,
|
|
|
|
ProgramDirectory: info.program,
|
|
|
|
EntryPoint: info.entryPoint,
|
|
|
|
Options: opts,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-08-19 15:55:54 +00:00
|
|
|
type InstallDependenciesRequest struct {
|
|
|
|
Info ProgramInfo
|
|
|
|
UseLanguageVersionTools bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (options InstallDependenciesRequest) String() string {
|
|
|
|
return fmt.Sprintf("Info=[%s], UseLanguageVersionTools=%t", options.Info, options.UseLanguageVersionTools)
|
|
|
|
}
|
|
|
|
|
2017-08-30 01:24:12 +00:00
|
|
|
// LanguageRuntime is a convenient interface for interacting with language runtime plugins. These tend to be
|
|
|
|
// dynamically loaded as plugins, although this interface hides this fact from the calling code.
|
|
|
|
type LanguageRuntime interface {
|
|
|
|
// Closer closes any underlying OS resources associated with this plugin (like processes, RPC channels, etc).
|
|
|
|
io.Closer
|
2018-02-06 17:57:32 +00:00
|
|
|
// GetRequiredPlugins computes the complete set of anticipated plugins required by a program.
|
2024-01-25 23:28:58 +00:00
|
|
|
GetRequiredPlugins(info ProgramInfo) ([]workspace.PluginSpec, error)
|
2019-03-20 18:54:32 +00:00
|
|
|
// Run executes a program in the language runtime for planning or deployment purposes. If
|
|
|
|
// info.DryRun is true, the code must not assume that side-effects or final values resulting
|
|
|
|
// from resource deployments are actually available. If it is false, on the other hand, a real
|
|
|
|
// deployment is occurring and it may safely depend on these.
|
|
|
|
//
|
|
|
|
// Returns a triple of "error message", "bail", or real "error". If "bail", the caller should
|
|
|
|
// return result.Bail immediately and not print any further messages to the user.
|
|
|
|
Run(info RunInfo) (string, bool, error)
|
2017-12-01 21:50:32 +00:00
|
|
|
// GetPluginInfo returns this plugin's information.
|
2018-02-06 17:57:32 +00:00
|
|
|
GetPluginInfo() (workspace.PluginInfo, error)
|
2022-04-03 14:54:59 +00:00
|
|
|
|
|
|
|
// InstallDependencies will install dependencies for the project, e.g. by running `npm install` for nodejs projects.
|
2024-08-19 15:55:54 +00:00
|
|
|
InstallDependencies(request InstallDependenciesRequest) error
|
2022-08-15 13:55:04 +00:00
|
|
|
|
2024-06-17 17:10:55 +00:00
|
|
|
// RuntimeOptions returns additional options that can be set for the runtime.
|
|
|
|
RuntimeOptionsPrompts(info ProgramInfo) ([]RuntimeOptionPrompt, error)
|
|
|
|
|
2022-08-15 13:55:04 +00:00
|
|
|
// About returns information about the language runtime.
|
2024-06-06 08:21:46 +00:00
|
|
|
About(info ProgramInfo) (AboutInfo, error)
|
2022-08-15 13:55:04 +00:00
|
|
|
|
|
|
|
// GetProgramDependencies returns information about the dependencies for the given program.
|
2024-01-25 23:28:58 +00:00
|
|
|
GetProgramDependencies(info ProgramInfo, transitiveDependencies bool) ([]DependencyInfo, error)
|
2022-10-04 08:58:01 +00:00
|
|
|
|
|
|
|
// RunPlugin executes a plugin program and returns its result asynchronously.
|
|
|
|
RunPlugin(info RunPluginInfo) (io.Reader, io.Reader, context.CancelFunc, error)
|
2022-10-17 14:21:11 +00:00
|
|
|
|
|
|
|
// GenerateProject generates a program project in the given directory. This will include metadata files such
|
|
|
|
// as Pulumi.yaml and package.json.
|
2023-07-27 09:27:07 +00:00
|
|
|
GenerateProject(sourceDirectory, targetDirectory, project string,
|
2023-08-03 10:40:05 +00:00
|
|
|
strict bool, loaderTarget string, localDependencies map[string]string) (hcl.Diagnostics, error)
|
2022-10-17 14:21:11 +00:00
|
|
|
|
|
|
|
// GeneratePlugin generates an SDK package.
|
2023-12-05 17:47:52 +00:00
|
|
|
GeneratePackage(
|
2024-03-26 13:10:34 +00:00
|
|
|
directory string, schema string, extraFiles map[string][]byte,
|
|
|
|
loaderTarget string, localDependencies map[string]string,
|
2024-08-07 08:16:37 +00:00
|
|
|
local bool,
|
2023-12-05 17:47:52 +00:00
|
|
|
) (hcl.Diagnostics, error)
|
2022-10-17 14:21:11 +00:00
|
|
|
|
|
|
|
// GenerateProgram is similar to GenerateProject but doesn't include any metadata files, just the program
|
|
|
|
// source code.
|
[cli/import] Fix undefined variable errors in code generation when imported resources use a parent or provider (#16786)
Fixes #15410 Fixes #13339
## Problem Context
When using `pulumi import` we generate code snippets for the resources
that were imported. Sometimes the user specifies `--parent
parentName=URN` or `--provider providerName=URN` which tweak the parent
or provider that the imported resources uses. When using `--parent` or
`--provider` the generated code emits a resource option `parent =
parentName` (in case of using `--parent`) where `parentName` is an
unbound variable.
Usually unbound variables would result in a _bind_ error such as `error:
undefined variable parentName` when type-checking the program however in
the import code generation we specify the bind option
`pcl.AllowMissingVariables` which turns that unbound variable errors
into warnings and code generation can continue to emit code.
This is all good and works as expected. However in the issues linked
above, we do get an _error_ for unbound variables in generated code even
though we specified `AllowMissingVariables`.
The problem as it turns out is when we are trying to generate code via
dynamically loaded `LangaugeRuntime` plugins. Specifically for NodeJS
and Python, we load `pulumi-language-nodejs` or `pulumi-language-python`
and call `GenerateProgram` to get the generated program. That function
`GenerateProgram` takes the text _SOURCE_ of the a bound program (one
that was bound using option `AllowMissingVariables`) and re-binds again
inside the implementation of the language plugin. The second time we
bind the program, we don't pass it the option `AllowMissingVariables`
and so it fails with `unboud variable` error.
I've verified that the issue above don't repro when doing an import for
dotnet (probably same for java/yaml) because we use the statically
linked function `codegen/{lang}/gen_program.go -> GenerateProgram`
## Solution
The problem can be solved by propagating the bind options from the CLI
to the language hosts during import so that they know how to bind the
program. I've extended the gRPC interface in `GenerateProgramRequest`
with a property `Strict` which follows the same logic from `pulumi
convert --strict` and made it such that the import command sends
`strict=false` to the language plugins when doing `GenerateProgram`.
This is consistent with `GenerateProject` that uses the same flag. When
`strict=false` we use `pcl.NonStrictBindOptions()` which includes
`AllowMissingVariables` .
## Repro
Once can test the before and after behaviour by running `pulumi up
--yes` on the following TypeScript program:
```ts
import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
export class MyComponent extends pulumi.ComponentResource {
public readonly randomPetId: pulumi.Output<string>;
constructor(name: string, opts?: pulumi.ComponentResourceOptions) {
super("example:index:MyComponent", name, {}, opts);
const randomPet = new random.RandomPet("randomPet", {}, {
parent: this
});
this.randomPetId = randomPet.id;
this.registerOutputs({
randomPetId: randomPet.id,
});
}
}
const example = new MyComponent("example");
export const randomPetId = example.randomPetId;
```
Then running `pulumi import -f import.json` where `import.json` contains
a resource to be imported under the created component (stack=`dev`,
project=`importerrors`)
```ts
{
"nameTable": {
"parentComponent": "urn:pulumi:dev::importerrors::example:index:MyComponent::example"
},
"resources": [
{
"type": "random:index/randomPassword:RandomPassword",
"name": "randomPassword",
"id": "supersecret",
"parent": "parentComponent"
}
]
}
```
Running this locally I get the following generated code (which
previously failed to generate)
```ts
import * as pulumi from "@pulumi/pulumi";
import * as random from "@pulumi/random";
const randomPassword = new random.RandomPassword("randomPassword", {
length: 11,
lower: true,
number: true,
numeric: true,
special: true,
upper: true,
}, {
parent: parentComponent,
});
```
2024-07-25 13:53:44 +00:00
|
|
|
GenerateProgram(program map[string]string, loaderTarget string,
|
|
|
|
strict bool) (map[string][]byte, hcl.Diagnostics, error)
|
2023-07-27 21:39:36 +00:00
|
|
|
|
|
|
|
// Pack packs a library package into a language specific artifact in the given destination directory.
|
Add SupportPack to schemas to write out in the new style (#15713)
<!---
Thanks so much for your contribution! If this is your first time
contributing, please ensure that you have read the
[CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md)
documentation.
-->
# Description
<!--- Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context. -->
This adds a new flag to the schema metadata to tell codegen to use the
new proposed style of SDKs where we fill in versions and write go.mods
etc.
I've reworked pack to operate on packages assuming they're in this new
style. That is pack no longer has the responsibility to fill in any
version information.
This updates python and node codegen to write out SDKs in this new
style, and fixes their core libraries to still be buildable via pack.
There are two approaches to fixing those, I've chosen option 1 below but
could pretty easily rework for option 2.
1) Write the version information directly to the SDKs at the same time
as we edit the .version file. To simplify this I've added a new
'set-version.py' script that takes a version string an writes it to all
the relevant places (.version, package.json, etc).
2) Write "pack" in the language host to search up the directory tree for
the ".version" file and then fill in the version information as we we're
doing before with envvar tricks and copying and editing package.json.
I think 1 is simpler long term, but does force some amount of cleanup in
unrelated bits of the system right now (release makefiles need a small
edit). 2 is much more localised but keeps this complexity that
sdk/nodejs sdk/python aren't actually valid source modules.
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [x] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-03-22 09:25:46 +00:00
|
|
|
Pack(packageDirectory string, destinationDirectory string) (string, error)
|
2022-08-15 13:55:04 +00:00
|
|
|
}
|
|
|
|
|
2024-02-09 22:18:42 +00:00
|
|
|
// DependencyInfo contains information about a dependency reported by a language runtime.
|
|
|
|
// These are the languages dependencies, they are not necessarily Pulumi packages.
|
2022-08-15 13:55:04 +00:00
|
|
|
type DependencyInfo struct {
|
2024-02-09 22:18:42 +00:00
|
|
|
// The name of the dependency.
|
|
|
|
Name string
|
|
|
|
// The version of the dependency. Unlike most versions in the system this is not guaranteed to be a semantic
|
|
|
|
// version.
|
|
|
|
Version string
|
2022-08-15 13:55:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type AboutInfo struct {
|
|
|
|
Executable string
|
|
|
|
Version string
|
|
|
|
Metadata map[string]string
|
2018-02-06 17:57:32 +00:00
|
|
|
}
|
|
|
|
|
2022-10-04 08:58:01 +00:00
|
|
|
type RunPluginInfo struct {
|
2024-01-25 23:28:58 +00:00
|
|
|
Info ProgramInfo
|
|
|
|
WorkingDirectory string
|
|
|
|
Args []string
|
|
|
|
Env []string
|
2017-08-30 01:24:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// RunInfo contains all of the information required to perform a plan or deployment operation.
|
|
|
|
type RunInfo struct {
|
2024-01-25 23:28:58 +00:00
|
|
|
Info ProgramInfo // the information about the program to run.
|
2023-10-20 10:44:16 +00:00
|
|
|
MonitorAddress string // the RPC address to the host resource monitor.
|
|
|
|
Project string // the project name housing the program being run.
|
|
|
|
Stack string // the stack name being evaluated.
|
|
|
|
Pwd string // the program's working directory.
|
|
|
|
Args []string // any arguments to pass to the program.
|
|
|
|
Config map[config.Key]string // the configuration variables to apply before running.
|
|
|
|
ConfigSecretKeys []config.Key // the configuration keys that have secret values.
|
|
|
|
ConfigPropertyMap resource.PropertyMap // the configuration as a property map.
|
|
|
|
DryRun bool // true if we are performing a dry-run (preview).
|
|
|
|
QueryMode bool // true if we're only doing a query.
|
2024-08-28 13:45:17 +00:00
|
|
|
Parallel int32 // the degree of parallelism for resource operations (<=1 for serial).
|
2023-10-20 10:44:16 +00:00
|
|
|
Organization string // the organization name housing the program being run (might be empty).
|
2024-08-29 09:14:39 +00:00
|
|
|
LoaderAddress string // the RPC address of the host's schema loader.
|
2024-09-04 10:36:45 +00:00
|
|
|
AttachDebugger bool // true if we are starting the program under a debugger.
|
2017-08-30 01:24:12 +00:00
|
|
|
}
|
2024-06-17 17:10:55 +00:00
|
|
|
|
|
|
|
type RuntimeOptionType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
PromptTypeString RuntimeOptionType = iota
|
|
|
|
PromptTypeInt32
|
|
|
|
)
|
|
|
|
|
|
|
|
// RuntimeOptionValue represents a single value that can be selected for a runtime option.
|
|
|
|
// The value can be either a string or an int32.
|
|
|
|
type RuntimeOptionValue struct {
|
|
|
|
PromptType RuntimeOptionType
|
|
|
|
StringValue string
|
|
|
|
Int32Value int32
|
2024-06-28 23:21:55 +00:00
|
|
|
DisplayName string
|
2024-06-17 17:10:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (v RuntimeOptionValue) Value() interface{} {
|
|
|
|
if v.PromptType == PromptTypeString {
|
|
|
|
return v.StringValue
|
|
|
|
}
|
|
|
|
return v.Int32Value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v RuntimeOptionValue) String() string {
|
|
|
|
if v.PromptType == PromptTypeString {
|
|
|
|
return v.StringValue
|
|
|
|
}
|
|
|
|
return strconv.Itoa(int(v.Int32Value))
|
|
|
|
}
|
|
|
|
|
|
|
|
func RuntimeOptionValueFromString(promptType RuntimeOptionType, value string) (RuntimeOptionValue, error) {
|
|
|
|
switch promptType {
|
|
|
|
case PromptTypeString:
|
|
|
|
return RuntimeOptionValue{PromptType: PromptTypeString, StringValue: value}, nil
|
|
|
|
case PromptTypeInt32:
|
|
|
|
return RuntimeOptionValue{PromptType: PromptTypeInt32, Int32Value: 0}, nil
|
|
|
|
default:
|
|
|
|
return RuntimeOptionValue{}, fmt.Errorf("unknown prompt type %d", promptType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RuntimeOptionPrompt is a prompt for a runtime option. The prompt can have multiple choices or
|
|
|
|
// be free-form if Choices is empty.
|
|
|
|
// Key is the key as used in runtime.options.<Key> in the Pulumi.yaml file.
|
|
|
|
type RuntimeOptionPrompt struct {
|
|
|
|
Key string
|
|
|
|
Description string
|
|
|
|
Choices []RuntimeOptionValue
|
|
|
|
Default *RuntimeOptionValue
|
|
|
|
PromptType RuntimeOptionType
|
|
|
|
}
|
|
|
|
|
|
|
|
func UnmarshallRuntimeOptionPrompt(p *pulumirpc.RuntimeOptionPrompt) (RuntimeOptionPrompt, error) {
|
|
|
|
choices := make([]RuntimeOptionValue, 0, len(p.Choices))
|
|
|
|
for _, choice := range p.Choices {
|
|
|
|
choices = append(choices, RuntimeOptionValue{
|
|
|
|
PromptType: RuntimeOptionType(choice.PromptType),
|
|
|
|
StringValue: choice.StringValue,
|
|
|
|
Int32Value: choice.Int32Value,
|
2024-06-28 23:21:55 +00:00
|
|
|
DisplayName: choice.DisplayName,
|
2024-06-17 17:10:55 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
var defaultValue *RuntimeOptionValue
|
|
|
|
if p.Default != nil {
|
|
|
|
defaultValue = &RuntimeOptionValue{
|
|
|
|
PromptType: RuntimeOptionType(p.Default.PromptType),
|
|
|
|
StringValue: p.Default.StringValue,
|
|
|
|
Int32Value: p.Default.Int32Value,
|
2024-06-28 23:21:55 +00:00
|
|
|
DisplayName: p.Default.DisplayName,
|
2024-06-17 17:10:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return RuntimeOptionPrompt{
|
|
|
|
Key: p.Key,
|
|
|
|
Description: p.Description,
|
|
|
|
Choices: choices,
|
|
|
|
Default: defaultValue,
|
|
|
|
PromptType: RuntimeOptionType(p.PromptType),
|
|
|
|
}, nil
|
|
|
|
}
|
2024-06-28 23:21:55 +00:00
|
|
|
|
|
|
|
// MakeRuntimeOptionPromptChoices creates a list of runtime option values from a list of executable names.
|
|
|
|
// If an executable is not found, it will be listed with a `[not found]` suffix at the end of the list.
|
|
|
|
func MakeExecutablePromptChoices(executables ...string) []*pulumirpc.RuntimeOptionPrompt_RuntimeOptionValue {
|
|
|
|
type packagemanagers struct {
|
|
|
|
name string
|
|
|
|
found bool
|
|
|
|
}
|
|
|
|
pms := []packagemanagers{}
|
|
|
|
for _, pm := range executables {
|
|
|
|
found := true
|
|
|
|
if _, err := exec.LookPath(pm); err != nil {
|
|
|
|
found = false
|
|
|
|
}
|
|
|
|
pms = append(pms, packagemanagers{name: pm, found: found})
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.SliceStable(pms, func(i, j int) bool {
|
|
|
|
// Don't reorder if both are found or both are not found.
|
|
|
|
if pms[i].found == pms[j].found {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// pms[i] is less than pms[j] if pms[i] is found.
|
|
|
|
return pms[i].found
|
|
|
|
})
|
|
|
|
|
|
|
|
choices := []*pulumirpc.RuntimeOptionPrompt_RuntimeOptionValue{}
|
|
|
|
for _, pm := range pms {
|
|
|
|
displayName := pm.name
|
|
|
|
if !pm.found {
|
|
|
|
displayName += " [not found]"
|
|
|
|
}
|
|
|
|
choices = append(choices, &pulumirpc.RuntimeOptionPrompt_RuntimeOptionValue{
|
|
|
|
PromptType: pulumirpc.RuntimeOptionPrompt_STRING,
|
|
|
|
StringValue: pm.name,
|
|
|
|
DisplayName: displayName,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return choices
|
|
|
|
}
|