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.
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
Overhaul resources, planning, and environments
This change, part of pulumi/lumi#90, overhauls quite a bit of the
core resource, planning, environments, and related areas.
The biggest amount of movement comes from the splitting of pkg/resource
into multiple sub-packages. This results in:
- pkg/resource: just the core resource data structures.
- pkg/resource/deployment: all planning and deployment logic.
- pkg/resource/environment: all environment, configuration, and
serialized checkpoint structures and logic.
- pkg/resource/plugin: all dynamically loaded analyzer and
provider logic, including the actual loading and RPC mechanisms.
This also splits the resource abstraction up. We now have:
- resource.Resource: a shared interface.
- resource.Object: a resource that is connected to a live object
that will periodically observe mutations due to ongoing
evaluation of computations. Snapshots of its state may be
taken; however, this is purely a "pre-planning" abstraction.
- resource.State: a snapshot of a resource's state that is frozen.
In other words, it is no longer connected to a live object.
This is what will store provider outputs (ID and properties),
and is what may be serialized into a deployment record.
The branch is in a half-baked state as of this change; more changes
are to come...
2017-06-08 23:37:40 +00:00
|
|
|
package plugin
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
|
|
|
import (
|
2019-12-16 22:51:02 +00:00
|
|
|
"encoding/json"
|
2024-09-09 11:11:46 +00:00
|
|
|
"errors"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
"fmt"
|
2019-12-16 22:51:02 +00:00
|
|
|
"os"
|
2020-03-18 23:15:57 +00:00
|
|
|
"path/filepath"
|
2020-03-08 21:11:55 +00:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
2023-12-12 12:19:42 +00:00
|
|
|
"strconv"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
"strings"
|
|
|
|
|
2018-02-06 17:57:32 +00:00
|
|
|
"github.com/blang/semver"
|
2022-11-01 15:15:09 +00:00
|
|
|
"google.golang.org/grpc"
|
2019-10-25 15:29:02 +00:00
|
|
|
"google.golang.org/grpc/codes"
|
2023-01-11 19:54:31 +00:00
|
|
|
"google.golang.org/grpc/credentials/insecure"
|
2024-01-17 09:35:20 +00:00
|
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
|
|
"google.golang.org/protobuf/types/known/structpb"
|
2019-10-09 20:51:10 +00:00
|
|
|
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
2023-06-28 16:02:04 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/slice"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
|
2022-11-01 15:15:09 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/rpcutil/rpcerror"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/workspace"
|
|
|
|
pulumirpc "github.com/pulumi/pulumi/sdk/v3/proto/go"
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// analyzer reflects an analyzer plugin, loaded dynamically for a single suite of checks.
|
|
|
|
type analyzer struct {
|
2020-05-22 22:01:15 +00:00
|
|
|
ctx *Context
|
|
|
|
name tokens.QName
|
|
|
|
plug *plugin
|
|
|
|
client pulumirpc.AnalyzerClient
|
|
|
|
version string
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2019-06-24 02:02:37 +00:00
|
|
|
var _ Analyzer = (*analyzer)(nil)
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
// NewAnalyzer binds to a given analyzer's plugin by name and creates a gRPC connection to it. If the associated plugin
|
|
|
|
// could not be found by name on the PATH, or an error occurs while creating the child process, an error is returned.
|
Introduce an interface to read config
This change adds an engine gRPC interface, and associated implementation,
so that plugins may do interesting things that require "phoning home".
Previously, the engine would fire up plugins and talk to them directly,
but there was no way for a plugin to ask the engine to do anything.
The motivation here is so that plugins can read evaluator state, such
as config information, but this change also allows richer logging
functionality than previously possible. We will still auto-log any
stdout/stderr writes; however, explicit errors, warnings, informational,
and even debug messages may be written over the Log API.
2017-06-21 02:45:07 +00:00
|
|
|
func NewAnalyzer(host Host, ctx *Context, name tokens.QName) (Analyzer, error) {
|
2018-02-06 15:20:04 +00:00
|
|
|
// Load the plugin's path by using the standard workspace logic.
|
2023-08-07 12:15:57 +00:00
|
|
|
path, err := workspace.GetPluginPath(ctx.Diag,
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.AnalyzerPlugin, strings.ReplaceAll(string(name), tokens.QNameDelimiter, "_"),
|
2022-07-22 13:17:43 +00:00
|
|
|
nil, host.GetProjectPlugins())
|
2018-02-06 15:20:04 +00:00
|
|
|
if err != nil {
|
2018-03-29 00:07:35 +00:00
|
|
|
return nil, rpcerror.Convert(err)
|
2018-02-06 15:20:04 +00:00
|
|
|
}
|
2023-02-15 01:06:56 +00:00
|
|
|
contract.Assertf(path != "", "unexpected empty path for analyzer plugin %s", name)
|
2018-02-06 15:20:04 +00:00
|
|
|
|
2022-11-01 15:15:09 +00:00
|
|
|
dialOpts := rpcutil.OpenTracingInterceptorDialOptions()
|
|
|
|
|
2019-11-18 18:29:12 +00:00
|
|
|
plug, err := newPlugin(ctx, ctx.Pwd, path, fmt.Sprintf("%v (analyzer)", name),
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.AnalyzerPlugin, []string{host.ServerAddr(), ctx.Pwd}, nil /*env*/, dialOpts)
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-02-06 15:20:04 +00:00
|
|
|
contract.Assertf(plug != nil, "unexpected nil analyzer plugin for %s", name)
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
|
|
|
|
return &analyzer{
|
|
|
|
ctx: ctx,
|
|
|
|
name: name,
|
|
|
|
plug: plug,
|
2017-12-01 21:50:32 +00:00
|
|
|
client: pulumirpc.NewAnalyzerClient(plug.Conn),
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2019-06-30 23:34:39 +00:00
|
|
|
// NewPolicyAnalyzer boots the nodejs analyzer plugin located at `policyPackpath`
|
|
|
|
func NewPolicyAnalyzer(
|
2023-03-03 16:36:39 +00:00
|
|
|
host Host, ctx *Context, name tokens.QName, policyPackPath string, opts *PolicyAnalyzerOptions,
|
|
|
|
) (Analyzer, error) {
|
2020-05-22 22:01:15 +00:00
|
|
|
projPath := filepath.Join(policyPackPath, "PulumiPolicy.yaml")
|
|
|
|
proj, err := workspace.LoadPolicyPack(projPath)
|
2020-03-18 23:15:57 +00:00
|
|
|
if err != nil {
|
2024-09-09 11:11:46 +00:00
|
|
|
return nil, fmt.Errorf("failed to load Pulumi policy project located at %q: %w", policyPackPath, err)
|
2020-03-18 23:15:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// For historical reasons, the Node.js plugin name is just "policy".
|
|
|
|
// All other languages have the runtime appended, e.g. "policy-<runtime>".
|
|
|
|
policyAnalyzerName := "policy"
|
|
|
|
if !strings.EqualFold(proj.Runtime.Name(), "nodejs") {
|
2023-12-12 12:19:42 +00:00
|
|
|
policyAnalyzerName = "policy-" + proj.Runtime.Name()
|
2020-03-18 23:15:57 +00:00
|
|
|
}
|
|
|
|
|
2019-06-30 23:34:39 +00:00
|
|
|
// Load the policy-booting analyzer plugin (i.e., `pulumi-analyzer-${policyAnalyzerName}`).
|
2023-08-07 12:15:57 +00:00
|
|
|
pluginPath, err := workspace.GetPluginPath(ctx.Diag,
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.AnalyzerPlugin, policyAnalyzerName, nil, host.GetProjectPlugins())
|
2023-08-25 19:00:17 +00:00
|
|
|
|
|
|
|
var e *workspace.MissingError
|
|
|
|
if errors.As(err, &e) {
|
2019-10-09 20:51:10 +00:00
|
|
|
return nil, fmt.Errorf("could not start policy pack %q because the built-in analyzer "+
|
2019-07-25 23:17:06 +00:00
|
|
|
"plugin that runs policy plugins is missing. This might occur when the plugin "+
|
|
|
|
"directory is not on your $PATH, or when the installed version of the Pulumi SDK "+
|
|
|
|
"does not support resource policies", string(name))
|
2023-08-25 19:00:17 +00:00
|
|
|
} else if err != nil {
|
|
|
|
return nil, err
|
2019-06-30 23:34:39 +00:00
|
|
|
}
|
|
|
|
|
2019-12-16 22:51:02 +00:00
|
|
|
// Create the environment variables from the options.
|
2020-03-18 23:15:57 +00:00
|
|
|
env, err := constructEnv(opts, proj.Runtime.Name())
|
2019-12-16 22:51:02 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-18 18:29:12 +00:00
|
|
|
// The `pulumi-analyzer-policy` plugin is a script that looks for the '@pulumi/pulumi/cmd/run-policy-pack'
|
|
|
|
// node module and runs it with node. To allow non-node Pulumi programs (e.g. Python, .NET, Go, etc.) to
|
|
|
|
// run node policy packs, we must set the plugin's pwd to the policy pack directory instead of the Pulumi
|
|
|
|
// program directory, so that the '@pulumi/pulumi/cmd/run-policy-pack' module from the policy pack's
|
|
|
|
// node_modules is used.
|
|
|
|
pwd := policyPackPath
|
2020-05-22 22:01:15 +00:00
|
|
|
|
|
|
|
args := []string{host.ServerAddr(), "."}
|
|
|
|
for k, v := range proj.Runtime.Options() {
|
|
|
|
if vstr := fmt.Sprintf("%v", v); vstr != "" {
|
|
|
|
args = append(args, fmt.Sprintf("-%s=%s", k, vstr))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-04 08:58:01 +00:00
|
|
|
plug, err := newPlugin(ctx, pwd, pluginPath, fmt.Sprintf("%v (analyzer)", name),
|
2024-04-25 17:30:30 +00:00
|
|
|
apitype.AnalyzerPlugin, args, env, analyzerPluginDialOptions(ctx, fmt.Sprintf("%v", name)))
|
2019-06-30 23:34:39 +00:00
|
|
|
if err != nil {
|
2020-02-28 20:48:53 +00:00
|
|
|
// The original error might have been wrapped before being returned from newPlugin. So we look for
|
|
|
|
// the root cause of the error. This won't work if we switch to Go 1.13's new approach to wrapping.
|
2024-09-09 11:11:46 +00:00
|
|
|
|
|
|
|
if errors.Is(err, errRunPolicyModuleNotFound) {
|
2019-11-19 18:47:47 +00:00
|
|
|
return nil, fmt.Errorf("it looks like the policy pack's dependencies are not installed; "+
|
|
|
|
"try running npm install or yarn install in %q", policyPackPath)
|
2019-10-09 20:51:10 +00:00
|
|
|
}
|
2024-09-09 11:11:46 +00:00
|
|
|
if errors.Is(err, errPluginNotFound) {
|
2020-02-28 20:48:53 +00:00
|
|
|
return nil, fmt.Errorf("policy pack not found at %q", name)
|
|
|
|
}
|
2024-09-09 11:11:46 +00:00
|
|
|
return nil, fmt.Errorf("policy pack %q failed to start: %w", string(name), err)
|
2019-06-30 23:34:39 +00:00
|
|
|
}
|
|
|
|
contract.Assertf(plug != nil, "unexpected nil analyzer plugin for %s", name)
|
|
|
|
|
|
|
|
return &analyzer{
|
2020-05-22 22:01:15 +00:00
|
|
|
ctx: ctx,
|
|
|
|
name: name,
|
|
|
|
plug: plug,
|
|
|
|
client: pulumirpc.NewAnalyzerClient(plug.Conn),
|
|
|
|
version: proj.Version,
|
2019-06-30 23:34:39 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
func (a *analyzer) Name() tokens.QName { return a.name }
|
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
// label returns a base label for tracing functions.
|
|
|
|
func (a *analyzer) label() string {
|
|
|
|
return fmt.Sprintf("Analyzer[%s]", a.name)
|
|
|
|
}
|
|
|
|
|
Make more progress on the new deployment model
This change restructures a lot more pertaining to deployments, snapshots,
environments, and the like.
The most notable change is that the notion of a deploy.Source is introduced,
which splits the responsibility between the deploy.Plan -- which simply
understands how to compute and carry out deployment plans -- and the idea
of something that can produce new objects on-demand during deployment.
The primary such implementation is evalSource, which encapsulates an
interpreter and takes a package, args, and config map, and proceeds to run
the interpreter in a distinct goroutine. It synchronizes as needed to
poke and prod the interpreter along its path to create new resource objects.
There are two other sources, however. First, a nullSource, which simply
refuses to create new objects. This can be handy when writing isolated
tests but is also used to simulate the "empty" environment as necessary to
do a complete teardown of the target environment. Second, a fixedSource,
which takes a pre-computed array of objects, and hands those, in order, to
the planning engine; this is mostly useful as a testing technique.
Boatloads of code is now changed and updated in the various CLI commands.
This further chugs along towards pulumi/lumi#90. The end is in sight.
2017-06-10 18:50:47 +00:00
|
|
|
// Analyze analyzes a single resource object, and returns any errors that it finds.
|
2019-10-25 15:29:02 +00:00
|
|
|
func (a *analyzer) Analyze(r AnalyzerResource) ([]AnalyzeDiagnostic, error) {
|
2019-11-21 21:01:15 +00:00
|
|
|
urn, t, name, props := r.URN, r.Type, r.Name, r.Properties
|
2019-06-10 22:20:44 +00:00
|
|
|
|
2017-12-15 15:22:49 +00:00
|
|
|
label := fmt.Sprintf("%s.Analyze(%s)", a.label(), t)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s executing (#props=%d)", label, len(props))
|
2020-03-18 18:39:13 +00:00
|
|
|
mprops, err := MarshalProperties(props,
|
|
|
|
MarshalOptions{KeepUnknowns: true, KeepSecrets: true, SkipInternalKeys: true})
|
2017-09-14 23:40:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2020-02-08 00:11:34 +00:00
|
|
|
provider, err := marshalProvider(r.Provider)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-12-01 21:50:32 +00:00
|
|
|
resp, err := a.client.Analyze(a.ctx.Request(), &pulumirpc.AnalyzeRequest{
|
2019-11-21 21:01:15 +00:00
|
|
|
Urn: string(urn),
|
2017-09-14 23:40:44 +00:00
|
|
|
Type: string(t),
|
2023-11-20 08:59:00 +00:00
|
|
|
Name: name,
|
2017-09-14 23:40:44 +00:00
|
|
|
Properties: mprops,
|
2020-02-08 00:11:34 +00:00
|
|
|
Options: marshalResourceOptions(r.Options),
|
|
|
|
Provider: provider,
|
2017-09-14 23:40:44 +00:00
|
|
|
})
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
if err != nil {
|
2018-03-29 00:07:35 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError)
|
2018-03-29 00:07:35 +00:00
|
|
|
return nil, rpcError
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2019-06-10 22:20:44 +00:00
|
|
|
failures := resp.GetDiagnostics()
|
|
|
|
logging.V(7).Infof("%s success: failures=#%d", label, len(failures))
|
|
|
|
|
2020-05-22 22:01:15 +00:00
|
|
|
diags, err := convertDiagnostics(failures, a.version)
|
2019-10-25 15:29:02 +00:00
|
|
|
if err != nil {
|
2024-09-09 11:11:46 +00:00
|
|
|
return nil, fmt.Errorf("converting analysis results: %w", err)
|
2019-10-25 15:29:02 +00:00
|
|
|
}
|
|
|
|
return diags, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AnalyzeStack analyzes all resources in a stack at the end of the update operation.
|
2020-02-08 00:11:34 +00:00
|
|
|
func (a *analyzer) AnalyzeStack(resources []AnalyzerStackResource) ([]AnalyzeDiagnostic, error) {
|
2019-10-25 15:29:02 +00:00
|
|
|
logging.V(7).Infof("%s.AnalyzeStack(#resources=%d) executing", a.label(), len(resources))
|
|
|
|
|
|
|
|
protoResources := make([]*pulumirpc.AnalyzerResource, len(resources))
|
|
|
|
for idx, resource := range resources {
|
2020-03-18 18:39:13 +00:00
|
|
|
props, err := MarshalProperties(resource.Properties,
|
|
|
|
MarshalOptions{KeepUnknowns: true, KeepSecrets: true, SkipInternalKeys: true})
|
2019-06-24 02:02:37 +00:00
|
|
|
if err != nil {
|
2024-09-09 11:11:46 +00:00
|
|
|
return nil, fmt.Errorf("marshalling properties: %w", err)
|
2019-06-10 22:20:44 +00:00
|
|
|
}
|
2019-06-24 02:02:37 +00:00
|
|
|
|
2020-02-08 00:11:34 +00:00
|
|
|
provider, err := marshalProvider(resource.Provider)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
propertyDeps := make(map[string]*pulumirpc.AnalyzerPropertyDependencies)
|
|
|
|
for pk, pd := range resource.PropertyDependencies {
|
|
|
|
// Skip properties that have no dependencies.
|
|
|
|
if len(pd) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-06-28 16:02:04 +00:00
|
|
|
pdeps := slice.Prealloc[string](1)
|
2020-02-08 00:11:34 +00:00
|
|
|
for _, d := range pd {
|
|
|
|
pdeps = append(pdeps, string(d))
|
|
|
|
}
|
|
|
|
propertyDeps[string(pk)] = &pulumirpc.AnalyzerPropertyDependencies{
|
|
|
|
Urns: pdeps,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-25 15:29:02 +00:00
|
|
|
protoResources[idx] = &pulumirpc.AnalyzerResource{
|
2020-02-08 00:11:34 +00:00
|
|
|
Urn: string(resource.URN),
|
|
|
|
Type: string(resource.Type),
|
2023-11-20 08:59:00 +00:00
|
|
|
Name: resource.Name,
|
2020-02-08 00:11:34 +00:00
|
|
|
Properties: props,
|
|
|
|
Options: marshalResourceOptions(resource.Options),
|
|
|
|
Provider: provider,
|
|
|
|
Parent: string(resource.Parent),
|
|
|
|
Dependencies: convertURNs(resource.Dependencies),
|
|
|
|
PropertyDependencies: propertyDeps,
|
2019-10-25 15:29:02 +00:00
|
|
|
}
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
2019-06-10 22:20:44 +00:00
|
|
|
|
2019-10-25 15:29:02 +00:00
|
|
|
resp, err := a.client.AnalyzeStack(a.ctx.Request(), &pulumirpc.AnalyzeStackRequest{
|
|
|
|
Resources: protoResources,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
// Handle the case where we the policy pack doesn't implement a recent enough
|
|
|
|
// AnalyzerService to support the AnalyzeStack method. Ignore the error as it
|
|
|
|
// just means the analyzer isn't capable of this specific type of check.
|
|
|
|
if rpcError.Code() == codes.Unimplemented {
|
|
|
|
logging.V(7).Infof("%s.AnalyzeStack(...) is unimplemented, skipping: err=%v", a.label(), rpcError)
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.V(7).Infof("%s.AnalyzeStack(...) failed: err=%v", a.label(), rpcError)
|
|
|
|
return nil, rpcError
|
|
|
|
}
|
|
|
|
|
|
|
|
failures := resp.GetDiagnostics()
|
|
|
|
logging.V(7).Infof("%s.AnalyzeStack(...) success: failures=#%d", a.label(), len(failures))
|
|
|
|
|
2020-05-22 22:01:15 +00:00
|
|
|
diags, err := convertDiagnostics(failures, a.version)
|
2019-10-25 15:29:02 +00:00
|
|
|
if err != nil {
|
2024-09-09 11:11:46 +00:00
|
|
|
return nil, fmt.Errorf("converting analysis results: %w", err)
|
2019-10-25 15:29:02 +00:00
|
|
|
}
|
2019-06-10 22:20:44 +00:00
|
|
|
return diags, nil
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
}
|
|
|
|
|
2023-10-09 18:31:17 +00:00
|
|
|
// Remediate is given the opportunity to transform a single resource, and returns its new properties.
|
|
|
|
func (a *analyzer) Remediate(r AnalyzerResource) ([]Remediation, error) {
|
|
|
|
urn, t, name, props := r.URN, r.Type, r.Name, r.Properties
|
|
|
|
|
|
|
|
label := fmt.Sprintf("%s.Remediate(%s)", a.label(), t)
|
|
|
|
logging.V(7).Infof("%s executing (#props=%d)", label, len(props))
|
|
|
|
mprops, err := MarshalProperties(props,
|
|
|
|
MarshalOptions{KeepUnknowns: true, KeepSecrets: true, SkipInternalKeys: false})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
provider, err := marshalProvider(r.Provider)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := a.client.Remediate(a.ctx.Request(), &pulumirpc.AnalyzeRequest{
|
|
|
|
Urn: string(urn),
|
|
|
|
Type: string(t),
|
2023-11-20 08:59:00 +00:00
|
|
|
Name: name,
|
2023-10-09 18:31:17 +00:00
|
|
|
Properties: mprops,
|
|
|
|
Options: marshalResourceOptions(r.Options),
|
|
|
|
Provider: provider,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
|
|
|
|
// Handle the case where we the policy pack doesn't implement a recent enough to implement Transform.
|
|
|
|
if rpcError.Code() == codes.Unimplemented {
|
|
|
|
logging.V(7).Infof("%s.Transform(...) is unimplemented, skipping: err=%v", a.label(), rpcError)
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError)
|
|
|
|
return nil, rpcError
|
|
|
|
}
|
|
|
|
|
|
|
|
remediations := resp.GetRemediations()
|
|
|
|
results := make([]Remediation, len(remediations))
|
|
|
|
for i, r := range remediations {
|
|
|
|
tprops, err := UnmarshalProperties(r.GetProperties(),
|
|
|
|
MarshalOptions{KeepUnknowns: true, KeepSecrets: true, SkipInternalKeys: false})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
results[i] = Remediation{
|
|
|
|
PolicyName: r.GetPolicyName(),
|
|
|
|
Description: r.GetDescription(),
|
|
|
|
PolicyPackName: r.GetPolicyPackName(),
|
|
|
|
PolicyPackVersion: r.GetPolicyPackVersion(),
|
|
|
|
Properties: tprops,
|
|
|
|
Diagnostic: r.GetDiagnostic(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.V(7).Infof("%s success: #remediations=%d", label, len(results))
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2019-07-13 01:32:50 +00:00
|
|
|
// GetAnalyzerInfo returns metadata about the policies contained in this analyzer plugin.
|
2019-06-24 02:02:37 +00:00
|
|
|
func (a *analyzer) GetAnalyzerInfo() (AnalyzerInfo, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := a.label() + ".GetAnalyzerInfo()"
|
2019-06-24 02:02:37 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
2024-01-17 09:35:20 +00:00
|
|
|
resp, err := a.client.GetAnalyzerInfo(a.ctx.Request(), &emptypb.Empty{})
|
2019-06-24 02:02:37 +00:00
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(7).Infof("%s failed: err=%v", a.label(), rpcError)
|
|
|
|
return AnalyzerInfo{}, rpcError
|
|
|
|
}
|
|
|
|
|
2020-03-08 21:11:55 +00:00
|
|
|
rpcPolicies := resp.GetPolicies()
|
|
|
|
policies := make([]AnalyzerPolicyInfo, len(rpcPolicies))
|
|
|
|
for i, p := range rpcPolicies {
|
2019-06-24 02:02:37 +00:00
|
|
|
enforcementLevel, err := convertEnforcementLevel(p.EnforcementLevel)
|
|
|
|
if err != nil {
|
|
|
|
return AnalyzerInfo{}, err
|
|
|
|
}
|
|
|
|
|
2020-03-08 21:11:55 +00:00
|
|
|
var schema *AnalyzerPolicyConfigSchema
|
|
|
|
if resp.GetSupportsConfig() {
|
|
|
|
schema = convertConfigSchema(p.GetConfigSchema())
|
|
|
|
|
|
|
|
// Inject `enforcementLevel` into the schema.
|
|
|
|
if schema == nil {
|
|
|
|
schema = &AnalyzerPolicyConfigSchema{}
|
|
|
|
}
|
|
|
|
if schema.Properties == nil {
|
|
|
|
schema.Properties = map[string]JSONSchema{}
|
|
|
|
}
|
|
|
|
schema.Properties["enforcementLevel"] = JSONSchema{
|
|
|
|
"type": "string",
|
2023-10-09 18:31:17 +00:00
|
|
|
"enum": []string{"advisory", "mandatory", "remediate", "disabled"},
|
2020-03-08 21:11:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
policies[i] = AnalyzerPolicyInfo{
|
2019-06-24 02:02:37 +00:00
|
|
|
Name: p.GetName(),
|
|
|
|
DisplayName: p.GetDisplayName(),
|
|
|
|
Description: p.GetDescription(),
|
|
|
|
EnforcementLevel: enforcementLevel,
|
|
|
|
Message: p.GetMessage(),
|
2020-03-08 21:11:55 +00:00
|
|
|
ConfigSchema: schema,
|
|
|
|
}
|
2019-06-24 02:02:37 +00:00
|
|
|
}
|
2020-03-08 21:11:55 +00:00
|
|
|
sort.Slice(policies, func(i, j int) bool {
|
|
|
|
return policies[i].Name < policies[j].Name
|
|
|
|
})
|
2019-06-24 02:02:37 +00:00
|
|
|
|
2020-03-30 19:52:05 +00:00
|
|
|
initialConfig := make(map[string]AnalyzerPolicyConfig)
|
|
|
|
for k, v := range resp.GetInitialConfig() {
|
|
|
|
enforcementLevel, err := convertEnforcementLevel(v.GetEnforcementLevel())
|
|
|
|
if err != nil {
|
|
|
|
return AnalyzerInfo{}, err
|
|
|
|
}
|
|
|
|
initialConfig[k] = AnalyzerPolicyConfig{
|
|
|
|
EnforcementLevel: enforcementLevel,
|
|
|
|
Properties: unmarshalMap(v.GetProperties()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 22:01:15 +00:00
|
|
|
// The version from PulumiPolicy.yaml is used, if set, over the version from the response.
|
|
|
|
version := resp.GetVersion()
|
|
|
|
if a.version != "" {
|
|
|
|
version = a.version
|
|
|
|
logging.V(7).Infof("Using version %q from PulumiPolicy.yaml", version)
|
|
|
|
}
|
|
|
|
|
2019-06-24 02:02:37 +00:00
|
|
|
return AnalyzerInfo{
|
2020-03-08 21:11:55 +00:00
|
|
|
Name: resp.GetName(),
|
|
|
|
DisplayName: resp.GetDisplayName(),
|
2020-05-22 22:01:15 +00:00
|
|
|
Version: version,
|
2020-03-08 21:11:55 +00:00
|
|
|
SupportsConfig: resp.GetSupportsConfig(),
|
|
|
|
Policies: policies,
|
2020-03-30 19:52:05 +00:00
|
|
|
InitialConfig: initialConfig,
|
2019-06-24 02:02:37 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-12-01 21:50:32 +00:00
|
|
|
// GetPluginInfo returns this plugin's information.
|
2018-02-06 17:57:32 +00:00
|
|
|
func (a *analyzer) GetPluginInfo() (workspace.PluginInfo, error) {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := a.label() + ".GetPluginInfo()"
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
2024-01-17 09:35:20 +00:00
|
|
|
resp, err := a.client.GetPluginInfo(a.ctx.Request(), &emptypb.Empty{})
|
2017-12-01 21:50:32 +00:00
|
|
|
if err != nil {
|
2018-03-29 00:07:35 +00:00
|
|
|
rpcError := rpcerror.Convert(err)
|
2018-05-15 22:28:00 +00:00
|
|
|
logging.V(7).Infof("%s failed: err=%v", a.label(), rpcError)
|
2018-03-29 00:07:35 +00:00
|
|
|
return workspace.PluginInfo{}, rpcError
|
2018-02-06 17:57:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var version *semver.Version
|
|
|
|
if v := resp.Version; v != "" {
|
|
|
|
sv, err := semver.ParseTolerant(v)
|
|
|
|
if err != nil {
|
|
|
|
return workspace.PluginInfo{}, err
|
|
|
|
}
|
|
|
|
version = &sv
|
2017-12-01 21:50:32 +00:00
|
|
|
}
|
2018-02-06 17:57:32 +00:00
|
|
|
|
|
|
|
return workspace.PluginInfo{
|
2018-02-21 18:32:31 +00:00
|
|
|
Name: string(a.name),
|
|
|
|
Path: a.plug.Bin,
|
2024-04-25 17:30:30 +00:00
|
|
|
Kind: apitype.AnalyzerPlugin,
|
2018-02-06 17:57:32 +00:00
|
|
|
Version: version,
|
2017-12-01 21:50:32 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-03-08 21:11:55 +00:00
|
|
|
func (a *analyzer) Configure(policyConfig map[string]AnalyzerPolicyConfig) error {
|
2023-12-12 12:19:42 +00:00
|
|
|
label := a.label() + ".Configure(...)"
|
2020-03-08 21:11:55 +00:00
|
|
|
logging.V(7).Infof("%s executing", label)
|
|
|
|
|
|
|
|
if len(policyConfig) == 0 {
|
|
|
|
logging.V(7).Infof("%s returning early, no config specified", label)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
c := make(map[string]*pulumirpc.PolicyConfig)
|
|
|
|
|
|
|
|
for k, v := range policyConfig {
|
|
|
|
if !v.EnforcementLevel.IsValid() {
|
2024-09-09 11:11:46 +00:00
|
|
|
return fmt.Errorf("invalid enforcement level %q", v.EnforcementLevel)
|
2020-03-08 21:11:55 +00:00
|
|
|
}
|
|
|
|
c[k] = &pulumirpc.PolicyConfig{
|
|
|
|
EnforcementLevel: marshalEnforcementLevel(v.EnforcementLevel),
|
|
|
|
Properties: marshalMap(v.Properties),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := a.client.Configure(a.ctx.Request(), &pulumirpc.ConfigureAnalyzerRequest{
|
|
|
|
PolicyConfig: c,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
rpcError := rpcerror.Convert(err)
|
|
|
|
logging.V(7).Infof("%s failed: err=%v", label, rpcError)
|
|
|
|
return rpcError
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
Add basic analyzer support
This change introduces the basic requirements for analyzers, as per
pulumi/coconut#119. In particular, an analyzer can implement either,
or both, of the RPC methods, Analyze and AnalyzeResource. The former
is meant to check an overall deployment (e.g., to ensure it has been
signed off on) and the latter is to check individual resources (e.g.,
to ensure properties of them are correct, such as checking style,
security, etc. rules). These run simultaneous to overall checking.
Analyzers are loaded as plugins just like providers are. The difference
is mainly in their naming ("analyzer-" prefix, rather than "resource-"),
and the RPC methods that they support.
This isn't 100% functional since we need a way to specify at the CLI
that a particular analyzer should be run, in addition to a way of
recording which analyzers certain projects should use in their manifests.
2017-03-11 07:49:17 +00:00
|
|
|
// Close tears down the underlying plugin RPC connection and process.
|
|
|
|
func (a *analyzer) Close() error {
|
|
|
|
return a.plug.Close()
|
|
|
|
}
|
2019-06-24 02:02:37 +00:00
|
|
|
|
2022-11-01 15:15:09 +00:00
|
|
|
func analyzerPluginDialOptions(ctx *Context, name string) []grpc.DialOption {
|
|
|
|
dialOpts := append(
|
|
|
|
rpcutil.OpenTracingInterceptorDialOptions(),
|
2023-01-11 19:54:31 +00:00
|
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
2022-11-01 15:15:09 +00:00
|
|
|
rpcutil.GrpcChannelOptions(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if ctx.DialOptions != nil {
|
|
|
|
metadata := map[string]interface{}{
|
|
|
|
"mode": "client",
|
|
|
|
"kind": "analyzer",
|
|
|
|
}
|
|
|
|
if name != "" {
|
|
|
|
metadata["name"] = name
|
|
|
|
}
|
|
|
|
dialOpts = append(dialOpts, ctx.DialOptions(metadata)...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return dialOpts
|
|
|
|
}
|
|
|
|
|
2020-02-08 00:11:34 +00:00
|
|
|
func marshalResourceOptions(opts AnalyzerResourceOptions) *pulumirpc.AnalyzerResourceOptions {
|
|
|
|
secs := make([]string, len(opts.AdditionalSecretOutputs))
|
|
|
|
for idx := range opts.AdditionalSecretOutputs {
|
|
|
|
secs[idx] = string(opts.AdditionalSecretOutputs[idx])
|
|
|
|
}
|
|
|
|
|
|
|
|
var deleteBeforeReplace bool
|
|
|
|
if opts.DeleteBeforeReplace != nil {
|
|
|
|
deleteBeforeReplace = *opts.DeleteBeforeReplace
|
|
|
|
}
|
|
|
|
|
|
|
|
result := &pulumirpc.AnalyzerResourceOptions{
|
|
|
|
Protect: opts.Protect,
|
|
|
|
IgnoreChanges: opts.IgnoreChanges,
|
|
|
|
DeleteBeforeReplace: deleteBeforeReplace,
|
|
|
|
DeleteBeforeReplaceDefined: opts.DeleteBeforeReplace != nil,
|
|
|
|
AdditionalSecretOutputs: secs,
|
2022-09-22 17:13:55 +00:00
|
|
|
Aliases: convertAliases(opts.Aliases, opts.AliasURNs),
|
2020-02-08 00:11:34 +00:00
|
|
|
CustomTimeouts: &pulumirpc.AnalyzerResourceOptions_CustomTimeouts{
|
|
|
|
Create: opts.CustomTimeouts.Create,
|
|
|
|
Update: opts.CustomTimeouts.Update,
|
|
|
|
Delete: opts.CustomTimeouts.Delete,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalProvider(provider *AnalyzerProviderResource) (*pulumirpc.AnalyzerProviderResource, error) {
|
|
|
|
if provider == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-03-18 18:39:13 +00:00
|
|
|
props, err := MarshalProperties(provider.Properties,
|
|
|
|
MarshalOptions{KeepUnknowns: true, KeepSecrets: true, SkipInternalKeys: true})
|
2020-02-08 00:11:34 +00:00
|
|
|
if err != nil {
|
2024-09-09 11:11:46 +00:00
|
|
|
return nil, fmt.Errorf("marshalling properties: %w", err)
|
2020-02-08 00:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &pulumirpc.AnalyzerProviderResource{
|
|
|
|
Urn: string(provider.URN),
|
|
|
|
Type: string(provider.Type),
|
2023-11-20 08:59:00 +00:00
|
|
|
Name: provider.Name,
|
2020-02-08 00:11:34 +00:00
|
|
|
Properties: props,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-03-08 21:11:55 +00:00
|
|
|
func marshalEnforcementLevel(el apitype.EnforcementLevel) pulumirpc.EnforcementLevel {
|
|
|
|
switch el {
|
|
|
|
case apitype.Advisory:
|
|
|
|
return pulumirpc.EnforcementLevel_ADVISORY
|
|
|
|
case apitype.Mandatory:
|
|
|
|
return pulumirpc.EnforcementLevel_MANDATORY
|
2023-10-09 18:31:17 +00:00
|
|
|
case apitype.Remediate:
|
|
|
|
return pulumirpc.EnforcementLevel_REMEDIATE
|
2020-03-08 21:11:55 +00:00
|
|
|
case apitype.Disabled:
|
|
|
|
return pulumirpc.EnforcementLevel_DISABLED
|
|
|
|
}
|
|
|
|
contract.Failf("Unrecognized enforcement level %s", el)
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalMap(m map[string]interface{}) *structpb.Struct {
|
|
|
|
fields := make(map[string]*structpb.Value)
|
|
|
|
for k, v := range m {
|
|
|
|
val := marshalMapValue(v)
|
|
|
|
if val != nil {
|
|
|
|
fields[k] = val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &structpb.Struct{
|
|
|
|
Fields: fields,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalMapValue(v interface{}) *structpb.Value {
|
|
|
|
if v == nil {
|
|
|
|
return &structpb.Value{
|
|
|
|
Kind: &structpb.Value_NullValue{
|
|
|
|
NullValue: structpb.NullValue_NULL_VALUE,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch val := v.(type) {
|
|
|
|
case bool:
|
|
|
|
return &structpb.Value{
|
|
|
|
Kind: &structpb.Value_BoolValue{
|
|
|
|
BoolValue: val,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case float64:
|
|
|
|
return &structpb.Value{
|
|
|
|
Kind: &structpb.Value_NumberValue{
|
|
|
|
NumberValue: val,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case string:
|
|
|
|
return &structpb.Value{
|
|
|
|
Kind: &structpb.Value_StringValue{
|
|
|
|
StringValue: val,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case []interface{}:
|
|
|
|
arr := make([]*structpb.Value, len(val))
|
|
|
|
for i, e := range val {
|
|
|
|
arr[i] = marshalMapValue(e)
|
|
|
|
}
|
|
|
|
return &structpb.Value{
|
|
|
|
Kind: &structpb.Value_ListValue{
|
|
|
|
ListValue: &structpb.ListValue{Values: arr},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
case map[string]interface{}:
|
|
|
|
return &structpb.Value{
|
|
|
|
Kind: &structpb.Value_StructValue{
|
|
|
|
StructValue: marshalMap(val),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
contract.Failf("Unrecognized value: %v (type=%v)", v, reflect.TypeOf(v))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func unmarshalMap(s *structpb.Struct) map[string]interface{} {
|
|
|
|
if s == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
result := make(map[string]interface{})
|
|
|
|
for k, v := range s.Fields {
|
|
|
|
result[k] = unmarshalMapValue(v)
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func unmarshalMapValue(v *structpb.Value) interface{} {
|
|
|
|
switch val := v.Kind.(type) {
|
|
|
|
case *structpb.Value_NullValue:
|
|
|
|
return nil
|
|
|
|
case *structpb.Value_BoolValue:
|
|
|
|
return val.BoolValue
|
|
|
|
case *structpb.Value_NumberValue:
|
|
|
|
return val.NumberValue
|
|
|
|
case *structpb.Value_StringValue:
|
|
|
|
return val.StringValue
|
|
|
|
case *structpb.Value_ListValue:
|
|
|
|
arr := make([]interface{}, len(val.ListValue.Values))
|
|
|
|
for i, e := range val.ListValue.Values {
|
|
|
|
arr[i] = unmarshalMapValue(e)
|
|
|
|
}
|
|
|
|
return arr
|
|
|
|
case *structpb.Value_StructValue:
|
|
|
|
return unmarshalMap(val.StructValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
contract.Failf("Unrecognized kind: %v (type=%v)", v.Kind, reflect.TypeOf(v.Kind))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-08 00:11:34 +00:00
|
|
|
func convertURNs(urns []resource.URN) []string {
|
|
|
|
result := make([]string, len(urns))
|
|
|
|
for idx := range urns {
|
|
|
|
result[idx] = string(urns[idx])
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-09-22 17:13:55 +00:00
|
|
|
func convertAlias(alias resource.Alias) string {
|
|
|
|
return string(alias.GetURN())
|
|
|
|
}
|
|
|
|
|
|
|
|
func convertAliases(aliases []resource.Alias, aliasURNs []resource.URN) []string {
|
|
|
|
result := make([]string, len(aliases)+len(aliasURNs))
|
|
|
|
for idx, alias := range aliases {
|
|
|
|
result[idx] = convertAlias(alias)
|
|
|
|
}
|
|
|
|
for idx, aliasURN := range aliasURNs {
|
|
|
|
result[idx+len(aliases)] = convertAlias(resource.Alias{URN: aliasURN})
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-06-24 02:02:37 +00:00
|
|
|
func convertEnforcementLevel(el pulumirpc.EnforcementLevel) (apitype.EnforcementLevel, error) {
|
|
|
|
switch el {
|
|
|
|
case pulumirpc.EnforcementLevel_ADVISORY:
|
|
|
|
return apitype.Advisory, nil
|
|
|
|
case pulumirpc.EnforcementLevel_MANDATORY:
|
|
|
|
return apitype.Mandatory, nil
|
2023-10-09 18:31:17 +00:00
|
|
|
case pulumirpc.EnforcementLevel_REMEDIATE:
|
|
|
|
return apitype.Remediate, nil
|
2020-03-08 21:11:55 +00:00
|
|
|
case pulumirpc.EnforcementLevel_DISABLED:
|
|
|
|
return apitype.Disabled, nil
|
2019-06-24 02:02:37 +00:00
|
|
|
|
|
|
|
default:
|
2022-10-09 14:58:33 +00:00
|
|
|
return "", fmt.Errorf("invalid enforcement level %d", el)
|
2019-06-24 02:02:37 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-25 15:29:02 +00:00
|
|
|
|
2020-03-08 21:11:55 +00:00
|
|
|
func convertConfigSchema(schema *pulumirpc.PolicyConfigSchema) *AnalyzerPolicyConfigSchema {
|
|
|
|
if schema == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
props := make(map[string]JSONSchema)
|
|
|
|
for k, v := range unmarshalMap(schema.GetProperties()) {
|
2022-10-25 07:42:05 +00:00
|
|
|
s := v.(map[string]interface{})
|
|
|
|
props[k] = JSONSchema(s)
|
2020-03-08 21:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &AnalyzerPolicyConfigSchema{
|
|
|
|
Properties: props,
|
|
|
|
Required: schema.GetRequired(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 22:01:15 +00:00
|
|
|
func convertDiagnostics(protoDiagnostics []*pulumirpc.AnalyzeDiagnostic, version string) ([]AnalyzeDiagnostic, error) {
|
2019-10-25 15:29:02 +00:00
|
|
|
diagnostics := make([]AnalyzeDiagnostic, len(protoDiagnostics))
|
|
|
|
for idx := range protoDiagnostics {
|
|
|
|
protoD := protoDiagnostics[idx]
|
|
|
|
|
2020-05-22 22:01:15 +00:00
|
|
|
// The version from PulumiPolicy.yaml is used, if set, over the version from the diagnostic.
|
|
|
|
policyPackVersion := protoD.PolicyPackVersion
|
|
|
|
if version != "" {
|
|
|
|
policyPackVersion = version
|
|
|
|
}
|
|
|
|
|
2019-10-25 15:29:02 +00:00
|
|
|
enforcementLevel, err := convertEnforcementLevel(protoD.EnforcementLevel)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
diagnostics[idx] = AnalyzeDiagnostic{
|
|
|
|
PolicyName: protoD.PolicyName,
|
|
|
|
PolicyPackName: protoD.PolicyPackName,
|
2020-05-22 22:01:15 +00:00
|
|
|
PolicyPackVersion: policyPackVersion,
|
2019-10-25 15:29:02 +00:00
|
|
|
Description: protoD.Description,
|
|
|
|
Message: protoD.Message,
|
|
|
|
Tags: protoD.Tags,
|
|
|
|
EnforcementLevel: enforcementLevel,
|
2019-11-21 21:01:15 +00:00
|
|
|
URN: resource.URN(protoD.Urn),
|
2019-10-25 15:29:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return diagnostics, nil
|
|
|
|
}
|
2019-12-16 22:51:02 +00:00
|
|
|
|
|
|
|
// constructEnv creates a slice of key/value pairs to be used as the environment for the policy pack process. Each entry
|
|
|
|
// is of the form "key=value". Config is passed as an environment variable (including unecrypted secrets), similar to
|
|
|
|
// how config is passed to each language runtime plugin.
|
2020-03-18 23:15:57 +00:00
|
|
|
func constructEnv(opts *PolicyAnalyzerOptions, runtime string) ([]string, error) {
|
2019-12-16 22:51:02 +00:00
|
|
|
env := os.Environ()
|
|
|
|
|
|
|
|
maybeAppendEnv := func(k, v string) {
|
|
|
|
if v != "" {
|
|
|
|
env = append(env, k+"="+v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
config, err := constructConfig(opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
maybeAppendEnv("PULUMI_CONFIG", config)
|
|
|
|
|
|
|
|
if opts != nil {
|
2020-03-18 23:15:57 +00:00
|
|
|
// Set both PULUMI_NODEJS_* and PULUMI_* environment variables for Node.js. The Node.js
|
|
|
|
// SDK currently looks for the PULUMI_NODEJS_* variants only, but we'd like to move to
|
|
|
|
// using the more general PULUMI_* variants for all languages to avoid special casing
|
|
|
|
// like this, and setting the PULUMI_* variants for Node.js is the first step.
|
|
|
|
if runtime == "nodejs" {
|
2022-09-02 09:47:38 +00:00
|
|
|
maybeAppendEnv("PULUMI_NODEJS_ORGANIZATION", opts.Organization)
|
2020-03-18 23:15:57 +00:00
|
|
|
maybeAppendEnv("PULUMI_NODEJS_PROJECT", opts.Project)
|
|
|
|
maybeAppendEnv("PULUMI_NODEJS_STACK", opts.Stack)
|
2023-12-12 12:19:42 +00:00
|
|
|
maybeAppendEnv("PULUMI_NODEJS_DRY_RUN", strconv.FormatBool(opts.DryRun))
|
2020-03-18 23:15:57 +00:00
|
|
|
}
|
|
|
|
|
2022-09-02 11:24:17 +00:00
|
|
|
maybeAppendEnv("PULUMI_ORGANIZATION", opts.Organization)
|
2020-03-18 23:15:57 +00:00
|
|
|
maybeAppendEnv("PULUMI_PROJECT", opts.Project)
|
|
|
|
maybeAppendEnv("PULUMI_STACK", opts.Stack)
|
2023-12-12 12:19:42 +00:00
|
|
|
maybeAppendEnv("PULUMI_DRY_RUN", strconv.FormatBool(opts.DryRun))
|
2019-12-16 22:51:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return env, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// constructConfig JSON-serializes the configuration data.
|
|
|
|
func constructConfig(opts *PolicyAnalyzerOptions) (string, error) {
|
|
|
|
if opts == nil || opts.Config == nil {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
config := make(map[string]string)
|
|
|
|
for k, v := range opts.Config {
|
|
|
|
config[k.String()] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
configJSON, err := json.Marshal(config)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(configJSON), nil
|
|
|
|
}
|