Merge branch 'master' of https://github.com/pulumi/pulumi into evan/auto

This commit is contained in:
evanboyle 2020-07-21 16:52:08 -07:00
commit c360e4d3f1
96 changed files with 4428 additions and 872 deletions

View file

@ -21,7 +21,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.6.10
python-version: 3.6.11
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi-cli@releases/v1
@ -54,7 +54,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.6.10
python-version: 3.6.11
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi-cli@releases/v1
@ -87,7 +87,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.6.10
python-version: 3.6.11
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi-cli@releases/v1
@ -120,7 +120,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.6.10
python-version: 3.6.11
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi-cli@releases/v1
@ -153,7 +153,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.6.10
python-version: 3.6.11
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi-cli@releases/v1
@ -186,7 +186,7 @@ jobs:
- name: Install Python
uses: actions/setup-python@v1
with:
python-version: 3.6.10
python-version: 3.6.11
- name: Install Pulumi CLI
uses: pulumi/action-install-pulumi-cli@releases/v1

View file

@ -14,7 +14,7 @@ jobs:
go-version: [1.14.x]
node-version: [10.x]
python-version: [3.7]
dotnet: ['3.1.100']
dotnet: ['3.1.301']
runs-on: ${{ matrix.platform }}
env:
GOPATH: ${{ github.workspace }}

View file

@ -10,7 +10,7 @@ jobs:
go-version: [1.14.x]
node-version: [10.x]
python-version: [3.7]
dotnet: ['3.1.100']
dotnet: ['3.1.301']
runs-on: ${{ matrix.platform }}
env:
GOPATH: ${{ github.workspace }}

View file

@ -3,12 +3,39 @@ CHANGELOG
## HEAD (Unreleased)
- Add pluginDownloadURL field to package definition
[#4947](https://github.com/pulumi/pulumi/pull/4947)
- Add support for streamInvoke during update
[#4990](https://github.com/pulumi/pulumi/pull/4990)
- Add ability to copy configuration values between stacks
[#4971](https://github.com/pulumi/pulumi/pull/4971)
- Add logic to parse pulumi venv on github action
[#4994](https://github.com/pulumi/pulumi/pull/4994)
- Better performance for stacks with many resources using the .NET SDK
[#5015](https://github.com/pulumi/pulumi/pull/5015)
- Output PDB files and enable SourceLink integration for .NET assemblies
[#4967](https://github.com/pulumi/pulumi/pull/4967)
## 2.6.1 (2020-07-09)
- Fix a panic in the display during CLI operations
[#4987](https://github.com/pulumi/pulumi/pull/4987)
## 2.6.0 (2020-07-08)
- Go program gen: Improved handling for pulumi.Map types
[#491](https://github.com/pulumi/pulumi/pull/4914)
[#4914](https://github.com/pulumi/pulumi/pull/4914)
- Go SDK: Input type interfaces should declare pointer type impls where appropriate
[#4911](https://github.com/pulumi/pulumi/pull/4911)
- Fixes issue where base64-encoded GOOGLE_CREDENTIALS causes problems with other commands
[#4972](https://github.com/pulumi/pulumi/pull/4972)
## 2.5.0 (2020-06-25)
- Go program gen: prompt array conversion, unused range vars, id handling
@ -379,7 +406,7 @@ CHANGELOG
[3938](https://github.com/pulumi/pulumi/pull/3938)
- Add support for transformations in the Go SDK.
[3978](https://github.com/pulumi/pulumi/pull/3938)
[3978](https://github.com/pulumi/pulumi/pull/3938)
## 1.11.0 (2020-02-19)
- Allow oversize protocol buffers for Python SDK.

View file

@ -79,9 +79,11 @@ fi
# For Google, we need to authenticate with a service principal for certain authentication operations.
if [ ! -z "$GOOGLE_CREDENTIALS" ]; then
export GOOGLE_APPLICATION_CREDENTIALS="$(mktemp).json"
# Check if GOOGLE_CREDENTIALS is base64 encoded
# Check if GOOGLE_CREDENTIALS is base64 encoded
if [[ $GOOGLE_CREDENTIALS =~ ^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$ ]]; then
echo "$GOOGLE_CREDENTIALS"|base64 -d > $GOOGLE_APPLICATION_CREDENTIALS
# unset for other gcloud commands using this variable.
unset GOOGLE_CREDENTIALS
else
echo "$GOOGLE_CREDENTIALS" > $GOOGLE_APPLICATION_CREDENTIALS
fi
@ -116,7 +118,16 @@ fi
# If the user is running the Python SDK, we will need to install their requirements as well.
if [ -e requirements.txt ]; then
pip3 install -r requirements.txt
# Check if should use venv
PULUMI_VENV=$(cat Pulumi.yaml | grep "virtualenv:" | cut -d':' -f2)
if [ -z $PULUMI_VENV ]; then
python3 -m venv $PULUMI_VENV
source $PULUMI_VENV/bin/activate
pip3 install -r requirements.txt
deactivate
else
pip3 install -r requirements.txt
fi
fi
# Now just pass along all arguments to the Pulumi CLI, sending the output to a file for

View file

@ -68,7 +68,7 @@ func ShowDiffEvents(op string, action apitype.UpdateKind,
out := os.Stdout
if event.Type == engine.DiagEvent {
payload := event.Payload.(engine.DiagEventPayload)
payload := event.Payload().(engine.DiagEventPayload)
if payload.Severity == diag.Error || payload.Severity == diag.Warning {
out = os.Stderr
}
@ -96,25 +96,26 @@ func RenderDiffEvent(action apitype.UpdateKind, event engine.Event,
// Currently, prelude, summary, and stdout events are printed the same for both the diff and
// progress displays.
case engine.PreludeEvent:
return renderPreludeEvent(event.Payload.(engine.PreludeEventPayload), opts)
return renderPreludeEvent(event.Payload().(engine.PreludeEventPayload), opts)
case engine.SummaryEvent:
return renderSummaryEvent(action, event.Payload.(engine.SummaryEventPayload), false /* wroteDiagnosticHeader */, opts)
const wroteDiagnosticHeader = false
return renderSummaryEvent(action, event.Payload().(engine.SummaryEventPayload), wroteDiagnosticHeader, opts)
case engine.StdoutColorEvent:
return renderStdoutColorEvent(event.Payload.(engine.StdoutEventPayload), opts)
return renderStdoutColorEvent(event.Payload().(engine.StdoutEventPayload), opts)
// Resource operations have very specific displays for either diff or progress displays.
// These functions should not be directly used by the progress display without validating
// that the display is appropriate for both.
case engine.ResourceOperationFailed:
return renderDiffResourceOperationFailedEvent(event.Payload.(engine.ResourceOperationFailedPayload), opts)
return renderDiffResourceOperationFailedEvent(event.Payload().(engine.ResourceOperationFailedPayload), opts)
case engine.ResourceOutputsEvent:
return renderDiffResourceOutputsEvent(event.Payload.(engine.ResourceOutputsEventPayload), seen, opts)
return renderDiffResourceOutputsEvent(event.Payload().(engine.ResourceOutputsEventPayload), seen, opts)
case engine.ResourcePreEvent:
return renderDiffResourcePreEvent(event.Payload.(engine.ResourcePreEventPayload), seen, opts)
return renderDiffResourcePreEvent(event.Payload().(engine.ResourcePreEventPayload), seen, opts)
case engine.DiagEvent:
return renderDiffDiagEvent(event.Payload.(engine.DiagEventPayload), opts)
return renderDiffDiagEvent(event.Payload().(engine.DiagEventPayload), opts)
case engine.PolicyViolationEvent:
return renderDiffPolicyViolationEvent(event.Payload.(engine.PolicyViolationEventPayload), opts)
return renderDiffPolicyViolationEvent(event.Payload().(engine.PolicyViolationEventPayload), opts)
default:
contract.Failf("unknown event type '%s'", event.Type)
@ -332,7 +333,7 @@ func renderDiffResourcePreEvent(
seen map[resource.URN]engine.StepEventMetadata,
opts Options) string {
seen[payload.Metadata.Res.URN] = payload.Metadata
seen[payload.Metadata.URN] = payload.Metadata
if payload.Metadata.Op == deploy.OpRefresh || payload.Metadata.Op == deploy.OpImport {
return ""
}
@ -360,7 +361,7 @@ func renderDiffResourceOutputsEvent(
indent := engine.GetIndent(payload.Metadata, seen)
refresh := false // are these outputs from a refresh?
if m, has := seen[payload.Metadata.Res.URN]; has && m.Op == deploy.OpRefresh {
if m, has := seen[payload.Metadata.URN]; has && m.Op == deploy.OpRefresh {
refresh = true
summary := engine.GetResourcePropertiesSummary(payload.Metadata, indent)
fprintIgnoreError(out, opts.Color.Colorize(summary))

View file

@ -119,7 +119,7 @@ func (s *nopSpinner) Reset() {
// isRootStack returns true if the step pertains to the rootmost stack component.
func isRootStack(step engine.StepEventMetadata) bool {
return isRootURN(step.Res.URN)
return isRootURN(step.URN)
}
func isRootURN(urn resource.URN) bool {

View file

@ -28,7 +28,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
apiEvent.CancelEvent = &apitype.CancelEvent{}
case engine.StdoutColorEvent:
p, ok := e.Payload.(engine.StdoutEventPayload)
p, ok := e.Payload().(engine.StdoutEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -38,7 +38,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.DiagEvent:
p, ok := e.Payload.(engine.DiagEventPayload)
p, ok := e.Payload().(engine.DiagEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -52,7 +52,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.PolicyViolationEvent:
p, ok := e.Payload.(engine.PolicyViolationEventPayload)
p, ok := e.Payload().(engine.PolicyViolationEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -68,7 +68,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.PreludeEvent:
p, ok := e.Payload.(engine.PreludeEventPayload)
p, ok := e.Payload().(engine.PreludeEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -82,7 +82,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.SummaryEvent:
p, ok := e.Payload.(engine.SummaryEventPayload)
p, ok := e.Payload().(engine.SummaryEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -99,7 +99,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.ResourcePreEvent:
p, ok := e.Payload.(engine.ResourcePreEventPayload)
p, ok := e.Payload().(engine.ResourcePreEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -109,7 +109,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.ResourceOutputsEvent:
p, ok := e.Payload.(engine.ResourceOutputsEventPayload)
p, ok := e.Payload().(engine.ResourceOutputsEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -119,7 +119,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
}
case engine.ResourceOperationFailed:
p, ok := e.Payload.(engine.ResourceOperationFailedPayload)
p, ok := e.Payload().(engine.ResourceOperationFailedPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
@ -175,8 +175,8 @@ func convertStepEventMetadata(md engine.StepEventMetadata) apitype.StepEventMeta
return apitype.StepEventMetadata{
Op: string(md.Op),
URN: string(md.Res.URN),
Type: string(md.Res.URN.Type()),
URN: string(md.URN),
Type: string(md.Type),
Old: convertStepEventStateMetadata(md.Old),
New: convertStepEventStateMetadata(md.New),

View file

@ -110,12 +110,12 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
// Events ocurring early:
case engine.PreludeEvent:
// Capture the config map from the prelude. Note that all secrets will remain blinded for safety.
digest.Config = e.Payload.(engine.PreludeEventPayload).Config
digest.Config = e.Payload().(engine.PreludeEventPayload).Config
// Events throughout the execution:
case engine.DiagEvent:
// Skip any ephemeral or debug messages, and elide all colorization.
p := e.Payload.(engine.DiagEventPayload)
p := e.Payload().(engine.DiagEventPayload)
if !p.Ephemeral && p.Severity != diag.Debug {
digest.Diagnostics = append(digest.Diagnostics, previewDiagnostic{
URN: p.URN,
@ -125,7 +125,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
}
case engine.StdoutColorEvent:
// Append stdout events as informational messages, and elide all colorization.
p := e.Payload.(engine.StdoutEventPayload)
p := e.Payload().(engine.StdoutEventPayload)
digest.Diagnostics = append(digest.Diagnostics, previewDiagnostic{
Message: colors.Never.Colorize(p.Message),
Severity: diag.Info,
@ -133,7 +133,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
case engine.ResourcePreEvent:
// Create the detailed metadata for this step and the initial state of its resource. Later,
// if new outputs arrive, we'll search for and swap in those new values.
if m := e.Payload.(engine.ResourcePreEventPayload).Metadata; shouldShow(m, opts) || isRootStack(m) {
if m := e.Payload().(engine.ResourcePreEventPayload).Metadata; shouldShow(m, opts) || isRootStack(m) {
var detailedDiff map[string]propertyDiff
if m.DetailedDiff != nil {
detailedDiff = make(map[string]propertyDiff)
@ -147,7 +147,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
step := &previewStep{
Op: m.Op,
URN: m.Res.URN,
URN: m.URN,
Provider: m.Provider,
DiffReasons: m.Diffs,
ReplaceReasons: m.Keys,
@ -183,7 +183,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
// Events ocurring late:
case engine.SummaryEvent:
// At the end of the preview, a summary event indicates the final conclusions.
p := e.Payload.(engine.SummaryEventPayload)
p := e.Payload().(engine.SummaryEventPayload)
digest.Duration = p.Duration
digest.ChangeSummary = p.ResourceChanges
digest.MaybeCorrupt = p.MaybeCorrupt

View file

@ -183,22 +183,23 @@ func simplifyTypeName(typ tokens.Type) string {
// event that has a URN. If this is also a 'step' event, then this will return the step metadata as
// well.
func getEventUrnAndMetadata(event engine.Event) (resource.URN, *engine.StepEventMetadata) {
if event.Type == engine.ResourcePreEvent {
payload := event.Payload.(engine.ResourcePreEventPayload)
return payload.Metadata.Res.URN, &payload.Metadata
} else if event.Type == engine.ResourceOutputsEvent {
payload := event.Payload.(engine.ResourceOutputsEventPayload)
return payload.Metadata.Res.URN, &payload.Metadata
} else if event.Type == engine.ResourceOperationFailed {
payload := event.Payload.(engine.ResourceOperationFailedPayload)
return payload.Metadata.Res.URN, &payload.Metadata
} else if event.Type == engine.DiagEvent {
return event.Payload.(engine.DiagEventPayload).URN, nil
} else if event.Type == engine.PolicyViolationEvent {
return event.Payload.(engine.PolicyViolationEventPayload).ResourceURN, nil
switch event.Type {
case engine.ResourcePreEvent:
payload := event.Payload().(engine.ResourcePreEventPayload)
return payload.Metadata.URN, &payload.Metadata
case engine.ResourceOutputsEvent:
payload := event.Payload().(engine.ResourceOutputsEventPayload)
return payload.Metadata.URN, &payload.Metadata
case engine.ResourceOperationFailed:
payload := event.Payload().(engine.ResourceOperationFailedPayload)
return payload.Metadata.URN, &payload.Metadata
case engine.DiagEvent:
return event.Payload().(engine.DiagEventPayload).URN, nil
case engine.PolicyViolationEvent:
return event.Payload().(engine.PolicyViolationEventPayload).ResourceURN, nil
default:
return "", nil
}
return "", nil
}
// Converts the colorization tags in a progress message and then actually writes the progress
@ -922,7 +923,7 @@ func (display *ProgressDisplay) getRowForURN(urn resource.URN, metadata *engine.
}
// First time we're hearing about this resource. Create an initial nearly-empty status for it.
step := engine.StepEventMetadata{Res: &engine.StepEventStateMetadata{URN: urn}, Op: deploy.OpSame}
step := engine.StepEventMetadata{URN: urn, Op: deploy.OpSame}
if metadata != nil {
step = *metadata
}
@ -962,18 +963,15 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
// A prelude event can just be printed out directly to the console.
// Note: we should probably make sure we don't get any prelude events
// once we start hearing about actual resource events.
payload := event.Payload.(engine.PreludeEventPayload)
payload := event.Payload().(engine.PreludeEventPayload)
preludeEventString := renderPreludeEvent(payload, display.opts)
if display.isTerminal {
display.processNormalEvent(engine.Event{
Type: engine.DiagEvent,
Payload: engine.DiagEventPayload{
Ephemeral: false,
Severity: diag.Info,
Color: cmdutil.GetGlobalColorization(),
Message: preludeEventString,
},
})
display.processNormalEvent(engine.NewEvent(engine.DiagEvent, engine.DiagEventPayload{
Ephemeral: false,
Severity: diag.Info,
Color: cmdutil.GetGlobalColorization(),
Message: preludeEventString,
}))
} else {
display.writeSimpleMessage(preludeEventString)
}
@ -981,16 +979,16 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
case engine.SummaryEvent:
// keep track of the summary event so that we can display it after all other
// resource-related events we receive.
payload := event.Payload.(engine.SummaryEventPayload)
payload := event.Payload().(engine.SummaryEventPayload)
display.summaryEventPayload = &payload
return
case engine.DiagEvent:
msg := display.renderProgressDiagEvent(event.Payload.(engine.DiagEventPayload), true /*includePrefix:*/)
msg := display.renderProgressDiagEvent(event.Payload().(engine.DiagEventPayload), true /*includePrefix:*/)
if msg == "" {
return
}
case engine.StdoutColorEvent:
display.handleSystemEvent(event.Payload.(engine.StdoutEventPayload))
display.handleSystemEvent(event.Payload().(engine.StdoutEventPayload))
return
}
@ -1012,15 +1010,12 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
// what's going on, we can show them as ephemeral diagnostic messages that are
// associated at the top level with the stack. That way if things are taking a while,
// there's insight in the display as to what's going on.
display.processNormalEvent(engine.Event{
Type: engine.DiagEvent,
Payload: engine.DiagEventPayload{
Ephemeral: true,
Severity: diag.Info,
Color: cmdutil.GetGlobalColorization(),
Message: fmt.Sprintf("read %v %v", simplifyTypeName(eventUrn.Type()), eventUrn.Name()),
},
})
display.processNormalEvent(engine.NewEvent(engine.DiagEvent, engine.DiagEventPayload{
Ephemeral: true,
Severity: diag.Info,
Color: cmdutil.GetGlobalColorization(),
Message: fmt.Sprintf("read %v %v", simplifyTypeName(eventUrn.Type()), eventUrn.Name()),
}))
return
}
}
@ -1047,14 +1042,14 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
}
if event.Type == engine.ResourcePreEvent {
step := event.Payload.(engine.ResourcePreEventPayload).Metadata
step := event.Payload().(engine.ResourcePreEventPayload).Metadata
row.SetStep(step)
} else if event.Type == engine.ResourceOutputsEvent {
isRefresh := display.getStepOp(row.Step()) == deploy.OpRefresh
step := event.Payload.(engine.ResourceOutputsEventPayload).Metadata
step := event.Payload().(engine.ResourceOutputsEventPayload).Metadata
// Is this the stack outputs event? If so, we'll need to print it out at the end of the plan.
if step.Res.URN == display.stackUrn {
if step.URN == display.stackUrn {
display.seenStackOutputs = true
}

View file

@ -57,7 +57,7 @@ func ShowQueryEvents(op string, events <-chan engine.Event,
out := os.Stdout
if event.Type == engine.DiagEvent {
payload := event.Payload.(engine.DiagEventPayload)
payload := event.Payload().(engine.DiagEventPayload)
if payload.Severity == diag.Error || payload.Severity == diag.Warning {
out = os.Stderr
}
@ -81,11 +81,11 @@ func renderQueryEvent(event engine.Event, opts Options) string {
return ""
case engine.StdoutColorEvent:
return renderStdoutColorEvent(event.Payload.(engine.StdoutEventPayload), opts)
return renderStdoutColorEvent(event.Payload().(engine.StdoutEventPayload), opts)
// Includes stdout of the query process.
case engine.DiagEvent:
return renderQueryDiagEvent(event.Payload.(engine.DiagEventPayload), opts)
return renderQueryDiagEvent(event.Payload().(engine.DiagEventPayload), opts)
case engine.PreludeEvent, engine.SummaryEvent, engine.ResourceOperationFailed,
engine.ResourceOutputsEvent, engine.ResourcePreEvent:

View file

@ -180,7 +180,7 @@ func (data *resourceRowData) DiagInfo() *DiagInfo {
}
func (data *resourceRowData) RecordDiagEvent(event engine.Event) {
payload := event.Payload.(engine.DiagEventPayload)
payload := event.Payload().(engine.DiagEventPayload)
data.recordDiagEventPayload(payload)
}
@ -223,7 +223,7 @@ func (data *resourceRowData) PolicyPayloads() []engine.PolicyViolationEventPaylo
// RecordPolicyViolationEvent records a policy event with the resourceRowData.
func (data *resourceRowData) RecordPolicyViolationEvent(event engine.Event) {
pePayload := event.Payload.(engine.PolicyViolationEventPayload)
pePayload := event.Payload().(engine.PolicyViolationEventPayload)
data.policyPayloads = append(data.policyPayloads, pePayload)
}
@ -272,7 +272,7 @@ func (data *resourceRowData) ContainsOutputsStep(op deploy.StepOp) bool {
func (data *resourceRowData) ColorizedSuffix() string {
if !data.IsDone() && data.display.isTerminal {
op := data.display.getStepOp(data.step)
if op != deploy.OpSame || isRootURN(data.step.Res.URN) {
if op != deploy.OpSame || isRootURN(data.step.URN) {
suffixes := data.display.suffixesArray
ellipses := suffixes[(data.tick+data.display.currentTick)%len(suffixes)]
@ -286,7 +286,7 @@ func (data *resourceRowData) ColorizedSuffix() string {
func (data *resourceRowData) ColorizedColumns() []string {
step := data.step
urn := data.step.Res.URN
urn := data.step.URN
if urn == "" {
// If we don't have a URN yet, mock parent it to the global stack.
urn = resource.DefaultRootStackURN(data.display.stack, data.display.proj)

View file

@ -52,7 +52,7 @@ func ShowWatchEvents(op string, action apitype.UpdateKind, events <-chan engine.
continue
case engine.DiagEvent:
// Skip any ephemeral or debug messages, and elide all colorization.
p := e.Payload.(engine.DiagEventPayload)
p := e.Payload().(engine.DiagEventPayload)
resourceName := ""
if p.URN != "" {
resourceName = string(p.URN.Name())
@ -60,22 +60,22 @@ func ShowWatchEvents(op string, action apitype.UpdateKind, events <-chan engine.
PrintfWithWatchPrefix(time.Now(), resourceName,
"%s", renderDiffDiagEvent(p, opts))
case engine.ResourcePreEvent:
p := e.Payload.(engine.ResourcePreEventPayload)
p := e.Payload().(engine.ResourcePreEventPayload)
if shouldShow(p.Metadata, opts) {
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.Res.URN.Name()),
"%s %s\n", p.Metadata.Op, p.Metadata.Res.URN.Type())
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
"%s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
}
case engine.ResourceOutputsEvent:
p := e.Payload.(engine.ResourceOutputsEventPayload)
p := e.Payload().(engine.ResourceOutputsEventPayload)
if shouldShow(p.Metadata, opts) {
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.Res.URN.Name()),
"done %s %s\n", p.Metadata.Op, p.Metadata.Res.URN.Type())
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
"done %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
}
case engine.ResourceOperationFailed:
p := e.Payload.(engine.ResourceOperationFailedPayload)
p := e.Payload().(engine.ResourceOperationFailedPayload)
if shouldShow(p.Metadata, opts) {
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.Res.URN.Name()),
"failed %s %s\n", p.Metadata.Op, p.Metadata.Res.URN.Type())
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
"failed %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
}
default:
contract.Failf("unknown event type '%s'", e.Type)

View file

@ -301,7 +301,7 @@ func (b *cloudBackend) getTarget(ctx context.Context, stackRef backend.StackRefe
}
func isDebugDiagEvent(e engine.Event) bool {
return e.Type == engine.DiagEvent && (e.Payload.(engine.DiagEventPayload)).Severity == diag.Debug
return e.Type == engine.DiagEvent && (e.Payload().(engine.DiagEventPayload)).Severity == diag.Debug
}
type engineEventBatch struct {

View file

@ -127,7 +127,7 @@ func (sm *SnapshotManager) RegisterResourceOutputs(step deploy.Step) error {
// intent to mutate before the mutation occurs.
func (sm *SnapshotManager) BeginMutation(step deploy.Step) (engine.SnapshotMutation, error) {
contract.Require(step != nil, "step != nil")
logging.V(9).Infof("SnapshotManager: Beginning mutation for step `%s` on resource `%s`", step.Op(), step.Res().URN)
logging.V(9).Infof("SnapshotManager: Beginning mutation for step `%s` on resource `%s`", step.Op(), step.URN())
switch step.Op() {
case deploy.OpSame:
@ -280,7 +280,7 @@ func (ssm *sameSnapshotMutation) End(step deploy.Step, successful bool) error {
}
func (sm *SnapshotManager) doCreate(step deploy.Step) (engine.SnapshotMutation, error) {
logging.V(9).Infof("SnapshotManager.doCreate(%s)", step.Res().URN)
logging.V(9).Infof("SnapshotManager.doCreate(%s)", step.URN())
err := sm.mutate(func() bool {
sm.markOperationPending(step.New(), resource.OperationTypeCreating)
return true
@ -324,7 +324,7 @@ func (csm *createSnapshotMutation) End(step deploy.Step, successful bool) error
}
func (sm *SnapshotManager) doUpdate(step deploy.Step) (engine.SnapshotMutation, error) {
logging.V(9).Infof("SnapshotManager.doUpdate(%s)", step.Res().URN)
logging.V(9).Infof("SnapshotManager.doUpdate(%s)", step.URN())
err := sm.mutate(func() bool {
sm.markOperationPending(step.New(), resource.OperationTypeUpdating)
return true
@ -354,7 +354,7 @@ func (usm *updateSnapshotMutation) End(step deploy.Step, successful bool) error
}
func (sm *SnapshotManager) doDelete(step deploy.Step) (engine.SnapshotMutation, error) {
logging.V(9).Infof("SnapshotManager.doDelete(%s)", step.Res().URN)
logging.V(9).Infof("SnapshotManager.doDelete(%s)", step.URN())
err := sm.mutate(func() bool {
sm.markOperationPending(step.Old(), resource.OperationTypeDeleting)
return true
@ -395,7 +395,7 @@ func (rsm *replaceSnapshotMutation) End(step deploy.Step, successful bool) error
}
func (sm *SnapshotManager) doRead(step deploy.Step) (engine.SnapshotMutation, error) {
logging.V(9).Infof("SnapshotManager.doRead(%s)", step.Res().URN)
logging.V(9).Infof("SnapshotManager.doRead(%s)", step.URN())
err := sm.mutate(func() bool {
sm.markOperationPending(step.New(), resource.OperationTypeReading)
return true
@ -460,7 +460,7 @@ func (rsm *removePendingReplaceSnapshotMutation) End(step deploy.Step, successfu
}
func (sm *SnapshotManager) doImport(step deploy.Step) (engine.SnapshotMutation, error) {
logging.V(9).Infof("SnapshotManager.doImport(%s)", step.Res().URN)
logging.V(9).Infof("SnapshotManager.doImport(%s)", step.URN())
err := sm.mutate(func() bool {
sm.markOperationPending(step.New(), resource.OperationTypeImporting)
return true

View file

@ -80,10 +80,162 @@ func newConfigCmd() *cobra.Command {
cmd.AddCommand(newConfigRmCmd(&stack))
cmd.AddCommand(newConfigSetCmd(&stack))
cmd.AddCommand(newConfigRefreshCmd(&stack))
cmd.AddCommand(newConfigCopyCmd(&stack))
return cmd
}
func newConfigCopyCmd(stack *string) *cobra.Command {
var path bool
var destinationStackName string
cpCommand := &cobra.Command{
Use: "cp [key]",
Short: "Copy config to another stack",
Long: "Copies the config from the current stack to the destination stack. If `key` is omitted,\n" +
"then all of the config from the current stack will be copied to the destination stack.",
Args: cmdutil.MaximumNArgs(1),
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
opts := display.Options{
Color: cmdutil.GetGlobalColorization(),
}
// Get current stack and ensure that it is a different stack to the destination stack
currentStack, err := requireStack(*stack, false, opts, true /*setCurrent*/)
if err != nil {
return err
}
if currentStack.Ref().Name().String() == destinationStackName {
return errors.New("current stack and destination stack are the same")
}
currentProjectStack, err := loadProjectStack(currentStack)
if err != nil {
return err
}
// Get the destination stack
destinationStack, err := requireStack(destinationStackName, false, opts, false /*setCurrent*/)
if err != nil {
return err
}
destinationProjectStack, err := loadProjectStack(destinationStack)
if err != nil {
return err
}
// Do we need to copy a single value or the entire map
if len(args) > 0 {
// A single key was specified so we only need to copy that specific value
return copySingleConfigKey(args[0], path, currentStack, currentProjectStack, destinationStack,
destinationProjectStack)
}
return copyEntireConfigMap(currentStack, currentProjectStack, destinationStack, destinationProjectStack)
}),
}
cpCommand.PersistentFlags().BoolVar(
&path, "path", false,
"The key contains a path to a property in a map or list to set")
cpCommand.PersistentFlags().StringVarP(
&destinationStackName, "dest", "d", "",
"The name of the new stack to copy the config to")
return cpCommand
}
func copySingleConfigKey(configKey string, path bool, currentStack backend.Stack,
currentProjectStack *workspace.ProjectStack, destinationStack backend.Stack,
destinationProjectStack *workspace.ProjectStack) error {
var decrypter config.Decrypter
key, err := parseConfigKey(configKey)
if err != nil {
return errors.Wrap(err, "invalid configuration key")
}
v, ok, err := currentProjectStack.Config.Get(key, path)
if err != nil {
return err
}
if ok {
if v.Secure() {
var err error
if decrypter, err = getStackDecrypter(currentStack); err != nil {
return errors.Wrap(err, "could not create a decrypter")
}
} else {
decrypter = config.NewPanicCrypter()
}
encrypter, cerr := getStackEncrypter(destinationStack)
if cerr != nil {
return cerr
}
val, err := v.Copy(decrypter, encrypter)
if err != nil {
return err
}
err = destinationProjectStack.Config.Set(key, val, path)
if err != nil {
return err
}
return saveProjectStack(destinationStack, destinationProjectStack)
}
return errors.Errorf(
"configuration key '%s' not found for stack '%s'", prettyKey(key), currentStack.Ref())
}
func copyEntireConfigMap(currentStack backend.Stack,
currentProjectStack *workspace.ProjectStack, destinationStack backend.Stack,
destinationProjectStack *workspace.ProjectStack) error {
var decrypter config.Decrypter
currentConfig := currentProjectStack.Config
if currentConfig.HasSecureValue() {
dec, decerr := getStackDecrypter(currentStack)
if decerr != nil {
return decerr
}
decrypter = dec
} else {
decrypter = config.NewPanicCrypter()
}
encrypter, cerr := getStackEncrypter(destinationStack)
if cerr != nil {
return cerr
}
newProjectConfig, err := currentConfig.Copy(decrypter, encrypter)
if err != nil {
return err
}
var requiresSaving bool
for key, val := range newProjectConfig {
err = destinationProjectStack.Config.Set(key, val, false)
if err != nil {
return err
}
requiresSaving = true
}
// The use of `requiresSaving` here ensures that there was actually some config
// that needed saved, otherwise it's an unnecessary save call
if requiresSaving {
err := saveProjectStack(destinationStack, destinationProjectStack)
if err != nil {
return err
}
}
return nil
}
func newConfigGetCmd(stack *string) *cobra.Command {
var jsonOut bool
var path bool
@ -431,7 +583,7 @@ func listConfig(stack backend.Stack, showSecrets bool, jsonOut bool) error {
// By default, we will use a blinding decrypter to show "[secret]". If requested, display secrets in plaintext.
decrypter := config.NewBlindingDecrypter()
if cfg.HasSecureValue() && showSecrets {
dec, decerr := getStackDencrypter(stack)
dec, decerr := getStackDecrypter(stack)
if decerr != nil {
return decerr
}
@ -518,7 +670,7 @@ func getConfig(stack backend.Stack, key config.Key, path, jsonOut bool) error {
var d config.Decrypter
if v.Secure() {
var err error
if d, err = getStackDencrypter(stack); err != nil {
if d, err = getStackDecrypter(stack); err != nil {
return errors.Wrap(err, "could not create a decrypter")
}
} else {

View file

@ -37,7 +37,7 @@ func getStackEncrypter(s backend.Stack) (config.Encrypter, error) {
return sm.Encrypter()
}
func getStackDencrypter(s backend.Stack) (config.Decrypter, error) {
func getStackDecrypter(s backend.Stack) (config.Decrypter, error) {
sm, err := getStackSecretsManager(s)
if err != nil {
return nil, err

View file

@ -61,7 +61,7 @@ This command lists data about previous updates for a stack.`,
}
var decrypter config.Decrypter
if showSecrets {
crypter, err := getStackDencrypter(s)
crypter, err := getStackDecrypter(s)
if err != nil {
return errors.Wrap(err, "decrypting secrets")
}

View file

@ -661,24 +661,18 @@ func (cancellationScopeSource) NewScope(events chan<- engine.Event, isPreview bo
message += colors.BrightRed + "Note that terminating immediately may lead to orphaned resources " +
"and other inconsistencies.\n" + colors.Reset
}
events <- engine.Event{
Type: engine.StdoutColorEvent,
Payload: engine.StdoutEventPayload{
Message: message,
Color: colors.Always,
},
}
events <- engine.NewEvent(engine.StdoutColorEvent, engine.StdoutEventPayload{
Message: message,
Color: colors.Always,
})
cancelSource.Cancel()
} else {
message := colors.BrightRed + "^C received; terminating" + colors.Reset
events <- engine.Event{
Type: engine.StdoutColorEvent,
Payload: engine.StdoutEventPayload{
Message: message,
Color: colors.Always,
},
}
events <- engine.NewEvent(engine.StdoutColorEvent, engine.StdoutEventPayload{
Message: message,
Color: colors.Always,
})
cancelSource.Terminate()
}

View file

@ -895,12 +895,41 @@ func (mod *modContext) getProperties(properties []*schema.Property, lang string,
return docProperties
}
func getDockerImagePythonFormalParams() []formalParam {
return []formalParam{
{
Name: "image_name",
},
{
Name: "build",
},
{
Name: "local_image_name",
DefaultValue: "=None",
},
{
Name: "registry",
DefaultValue: "=None",
},
{
Name: "skip_push",
DefaultValue: "=None",
},
{
Name: "opts",
DefaultValue: "=None",
},
}
}
// Returns the rendered HTML for the resource's constructor, as well as the specific arguments.
func (mod *modContext) genConstructors(r *schema.Resource, allOptionalInputs bool) (map[string]string, map[string][]formalParam) {
renderedParams := make(map[string]string)
formalParams := make(map[string][]formalParam)
isK8sOverlayMod := mod.isKubernetesOverlayModule()
isK8sPackage := isKubernetesPackage(mod.pkg)
isDockerImageResource := mod.pkg.Name == "docker" && resourceName(r) == "Image"
for _, lang := range supportedLanguages {
var (
paramTemplate string
@ -927,6 +956,9 @@ func (mod *modContext) genConstructors(r *schema.Resource, allOptionalInputs boo
if isK8sOverlayMod {
params = getKubernetesOverlayPythonFormalParams(mod.mod)
break
} else if isDockerImageResource {
params = getDockerImagePythonFormalParams()
break
}
params = make([]formalParam, 0, len(r.InputProperties)+1)

View file

@ -33,15 +33,17 @@ func isKubernetesPackage(pkg *schema.Package) bool {
func (mod *modContext) isKubernetesOverlayModule() bool {
// The CustomResource overlay resource is directly under the apiextensions module
// and not under a version, so we include that. The resources under helm and yaml are
// always under a version.
return mod.mod == "apiextensions" ||
// and not under a version, so we include that. The Directory overlay resource is directly under the
// kustomize module. The resources under helm and yaml are always under a version.
return mod.mod == "apiextensions" || mod.mod == "kustomize" ||
strings.HasPrefix(mod.mod, "helm") || strings.HasPrefix(mod.mod, "yaml")
}
func (mod *modContext) isComponentResource() bool {
// TODO: Support this more generally. For now, only the Helm and YAML overlays use ComponentResources.
return strings.HasPrefix(mod.mod, "helm") || strings.HasPrefix(mod.mod, "yaml")
// TODO: Support this more generally. For now, only the Helm, Kustomize, and YAML overlays use ComponentResources.
return strings.HasPrefix(mod.mod, "helm") ||
strings.HasPrefix(mod.mod, "kustomize") ||
strings.HasPrefix(mod.mod, "yaml")
}
// getKubernetesOverlayPythonFormalParams returns the formal params to render
@ -60,6 +62,24 @@ func getKubernetesOverlayPythonFormalParams(modName string) []formalParam {
DefaultValue: "=None",
},
}
case "kustomize":
params = []formalParam{
{
Name: "directory",
},
{
Name: "opts",
DefaultValue: "=None",
},
{
Name: "transformations",
DefaultValue: "=None",
},
{
Name: "resource_prefix",
DefaultValue: "=None",
},
}
case "yaml":
params = []formalParam{
{

View file

@ -31,7 +31,7 @@
{{ htmlSafe "{{% /choosable %}}" }}
{{ htmlSafe "{{% choosable language python %}}" }}
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def </span>{{ template "linkify_param" .ConstructorResource.python }}<span class="p">(resource_name, </span>{{ htmlSafe .ConstructorParams.python }}<span class="p">);</span></code></pre></div>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">def </span>{{ template "linkify_param" .ConstructorResource.python }}<span class="p">(resource_name, </span>{{ htmlSafe .ConstructorParams.python }}<span class="p">)</span></code></pre></div>
{{ htmlSafe "{{% /choosable %}}" }}
{{ htmlSafe "{{% choosable language go %}}" }}
@ -111,7 +111,7 @@ Get an existing {{.Header.Title}} resource's state with the given name, ID, and
{{ htmlSafe "{{% /choosable %}}" }}
{{ htmlSafe "{{% choosable language python %}}" }}
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">static </span><span class="nf">get</span><span class="p">(resource_name, id, opts=None, </span>{{ htmlSafe .LookupParams.python }}<span class="p">, __props__=None);</span></code></pre></div>
<div class="highlight"><pre class="chroma"><code class="language-python" data-lang="python"><span class="k">static </span><span class="nf">get</span><span class="p">(resource_name, id, opts=None, </span>{{ htmlSafe .LookupParams.python }}<span class="p">, __props__=None)</span></code></pre></div>
{{ htmlSafe "{{% /choosable %}}" }}
{{ htmlSafe "{{% choosable language go %}}" }}

View file

@ -129,6 +129,16 @@ const csharpProjectFileTemplateText = `<Project Sdk="Microsoft.NET.Sdk">
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="version.txt" />
<Content Include="version.txt" />

View file

@ -92,6 +92,14 @@ func camel(s string) string {
return string(res)
}
func tokenToPackage(pkg *schema.Package, overrides map[string]string, tok string) string {
mod := pkg.TokenToModule(tok)
if override, ok := overrides[mod]; ok {
mod = override
}
return strings.ToLower(mod)
}
type pkgContext struct {
pkg *schema.Package
mod string
@ -120,6 +128,10 @@ func (pkg *pkgContext) details(t *schema.ObjectType) *typeDetails {
return details
}
func (pkg *pkgContext) tokenToPackage(tok string) string {
return tokenToPackage(pkg.pkg, pkg.modToPkg, tok)
}
func (pkg *pkgContext) tokenToType(tok string) string {
// token := pkg : module : member
// module := path/to/module
@ -133,14 +145,13 @@ func (pkg *pkgContext) tokenToType(tok string) string {
panic(fmt.Errorf("pkg.pkg is nil. token %s", tok))
}
mod, name := pkg.pkg.TokenToModule(tok), components[2]
if override, ok := pkg.modToPkg[mod]; ok {
mod = override
}
mod, name := pkg.tokenToPackage(tok), components[2]
// If the package containing the type's token already has a resource with the
// same name, add a `Type` suffix.
modPkg := pkg.getPkg(mod)
modPkg, ok := pkg.packages[mod]
contract.Assert(ok)
name = Title(name)
if modPkg.names.has(name) {
name += "Type"
@ -149,6 +160,9 @@ func (pkg *pkgContext) tokenToType(tok string) string {
if mod == pkg.mod {
return name
}
if mod == "" {
mod = components[0]
}
return strings.Replace(mod, "/", "", -1) + "." + name
}
@ -921,10 +935,7 @@ func (pkg *pkgContext) getTypeImports(t schema.Type, recurse bool, imports strin
case *schema.MapType:
pkg.getTypeImports(t.ElementType, recurse, imports, seen)
case *schema.ObjectType:
mod := pkg.pkg.TokenToModule(t.Token)
if override, ok := pkg.modToPkg[mod]; ok {
mod = override
}
mod := pkg.tokenToPackage(t.Token)
if mod != pkg.mod {
imports.add(path.Join(pkg.importBasePath, mod))
}
@ -1071,25 +1082,10 @@ func (pkg *pkgContext) genConfig(w io.Writer, variables []*schema.Property) erro
return nil
}
func (pkg *pkgContext) getPkg(mod string) *pkgContext {
if override, ok := pkg.modToPkg[mod]; ok {
mod = override
}
pack, ok := pkg.packages[mod]
if !ok {
return nil
}
return pack
}
// generatePackageContextMap groups resources, types, and functions into Go packages.
func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackageInfo) map[string]*pkgContext {
packages := map[string]*pkgContext{}
getPkg := func(mod string) *pkgContext {
if override, ok := goInfo.ModuleToPackage[mod]; ok {
mod = override
}
pack, ok := packages[mod]
if !ok {
pack = &pkgContext{
@ -1110,7 +1106,7 @@ func generatePackageContextMap(tool string, pkg *schema.Package, goInfo GoPackag
}
getPkgFromToken := func(token string) *pkgContext {
return getPkg(pkg.TokenToModule(token))
return getPkg(tokenToPackage(pkg, goInfo.ModuleToPackage, token))
}
if len(pkg.Config) > 0 {

View file

@ -30,7 +30,7 @@ import (
type bindOptions struct {
allowMissingVariables bool
host plugin.Host
loader schema.Loader
packageCache *PackageCache
}
@ -59,8 +59,12 @@ func AllowMissingVariables(options *bindOptions) {
}
func PluginHost(host plugin.Host) BindOption {
return Loader(schema.NewPluginLoader(host))
}
func Loader(loader schema.Loader) BindOption {
return func(options *bindOptions) {
options.host = host
options.loader = loader
}
}
@ -78,7 +82,7 @@ func BindProgram(files []*syntax.File, opts ...BindOption) (*Program, hcl.Diagno
o(&options)
}
if options.host == nil {
if options.loader == nil {
cwd, err := os.Getwd()
if err != nil {
return nil, nil, err
@ -87,7 +91,7 @@ func BindProgram(files []*syntax.File, opts ...BindOption) (*Program, hcl.Diagno
if err != nil {
return nil, nil, err
}
options.host = ctx.Host
options.loader = schema.NewPluginLoader(ctx.Host)
defer contract.IgnoreClose(ctx)
}

View file

@ -21,12 +21,9 @@ import (
"github.com/blang/semver"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
jsoniter "github.com/json-iterator/go"
"github.com/pulumi/pulumi/pkg/v2/codegen"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
)
@ -60,29 +57,13 @@ func (c *PackageCache) getPackageSchema(name string) (*packageSchema, bool) {
// GetSchema method.
//
// TODO: schema and provider versions
func (c *PackageCache) loadPackageSchema(host plugin.Host, name string) (*packageSchema, error) {
func (c *PackageCache) loadPackageSchema(loader schema.Loader, name string) (*packageSchema, error) {
if s, ok := c.getPackageSchema(name); ok {
return s, nil
}
providerVersion := (*semver.Version)(nil)
provider, err := host.Provider(tokens.Package(name), providerVersion)
if err != nil {
return nil, err
}
schemaFormatVersion := 0
schemaBytes, err := provider.GetSchema(schemaFormatVersion)
if err != nil {
return nil, err
}
var spec schema.PackageSpec
if err := jsoniter.Unmarshal(schemaBytes, &spec); err != nil {
return nil, err
}
pkg, err := schema.ImportSpec(spec, nil)
version := (*semver.Version)(nil)
pkg, err := loader.LoadPackage(name, version)
if err != nil {
return nil, err
}
@ -153,7 +134,7 @@ func (b *binder) loadReferencedPackageSchemas(n Node) error {
if _, ok := b.referencedPackages[name]; ok {
continue
}
pkg, err := b.options.packageCache.loadPackageSchema(b.options.host, name)
pkg, err := b.options.packageCache.loadPackageSchema(b.options.loader, name)
if err != nil {
return err
}

View file

@ -4,14 +4,15 @@ import (
"testing"
"github.com/pulumi/pulumi/pkg/v2/codegen/internal/test"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
)
func BenchmarkLoadPackage(b *testing.B) {
host := test.NewHost(testdataPath)
loader := schema.NewPluginLoader(test.NewHost(testdataPath))
for n := 0; n < b.N; n++ {
_, err := NewPackageCache().loadPackageSchema(host, "aws")
_, err := NewPackageCache().loadPackageSchema(loader, "aws")
contract.AssertNoError(err)
}
}

View file

@ -49,6 +49,14 @@ func (a *Attribute) HasTrailingTrivia() bool {
return a.Value.HasTrailingTrivia()
}
func (a *Attribute) GetLeadingTrivia() syntax.TriviaList {
return a.Tokens.GetName(a.Name).LeadingTrivia
}
func (a *Attribute) GetTrailingTrivia() syntax.TriviaList {
return a.Value.GetTrailingTrivia()
}
func (a *Attribute) Format(f fmt.State, c rune) {
a.print(f, &printer{})
}

View file

@ -52,6 +52,14 @@ func (b *Block) HasTrailingTrivia() bool {
return b.Tokens != nil
}
func (b *Block) GetLeadingTrivia() syntax.TriviaList {
return b.Tokens.GetType(b.Type).LeadingTrivia
}
func (b *Block) GetTrailingTrivia() syntax.TriviaList {
return b.Tokens.GetCloseBrace().TrailingTrivia
}
func (b *Block) Format(f fmt.State, c rune) {
b.print(f, &printer{})
}
@ -94,9 +102,6 @@ func (b *Block) print(w io.Writer, p *printer) {
p.indented(func() {
b.Body.print(w, p)
})
if !b.Body.HasTrailingTrivia() {
p.fprintf(w, "\n")
}
if b.Tokens != nil {
p.fprintf(w, "%v", b.Tokens.GetCloseBrace())

View file

@ -61,6 +61,23 @@ func (b *Body) HasTrailingTrivia() bool {
return len(b.Items) > 0 && b.Items[len(b.Items)-1].HasTrailingTrivia()
}
func (b *Body) GetLeadingTrivia() syntax.TriviaList {
if len(b.Items) == 0 {
return nil
}
return b.Items[0].GetLeadingTrivia()
}
func (b *Body) GetTrailingTrivia() syntax.TriviaList {
if eof := b.Tokens.GetEndOfFile(); eof != nil {
return eof.TrailingTrivia
}
if len(b.Items) == 0 {
return nil
}
return b.Items[len(b.Items)-1].GetTrailingTrivia()
}
func (b *Body) Format(f fmt.State, c rune) {
b.print(f, &printer{})
}
@ -69,6 +86,9 @@ func (b *Body) print(w io.Writer, p *printer) {
// Print the items, separated by newlines.
for _, item := range b.Items {
p.fprintf(w, "% v", item)
if !item.GetTrailingTrivia().EndsOnNewLine() {
p.fprintf(w, "\n")
}
}
// If the body has an end-of-file token, print it.

View file

@ -37,12 +37,8 @@ type Expression interface {
// NodeTokens returns the syntax.Tokens associated with the expression.
NodeTokens() syntax.NodeTokens
// GetLeadingTrivia returns the leading trivia associated with the expression.
GetLeadingTrivia() syntax.TriviaList
// SetLeadingTrivia sets the leading trivia associated with the expression.
SetLeadingTrivia(syntax.TriviaList)
// GetTrailingTrivia returns the trailing trivia associated with the expression.
GetTrailingTrivia() syntax.TriviaList
// SetTrailingTrivia sets the trailing trivia associated with the expression.
SetTrailingTrivia(syntax.TriviaList)
@ -1556,6 +1552,7 @@ func (x *ObjectConsExpression) print(w io.Writer, p *printer) {
p.fprintf(w, "%(%v", x.Tokens.GetParentheses(), x.Tokens.GetOpenBrace(len(x.Items)))
// Print the items.
isMultiLine, trailingNewline := false, false
p.indented(func() {
items := x.Tokens.GetItems(len(x.Items))
for i, item := range x.Items {
@ -1564,7 +1561,12 @@ func (x *ObjectConsExpression) print(w io.Writer, p *printer) {
tokens = items[i]
}
if !item.Key.HasLeadingTrivia() {
if item.Key.HasLeadingTrivia() {
if _, i := item.Key.GetLeadingTrivia().Index("\n"); i != -1 {
isMultiLine = true
}
} else if len(items) > 1 {
isMultiLine = true
p.fprintf(w, "\n%s", p.indent)
}
p.fprintf(w, "%v% v% v", item.Key, tokens.Equals, item.Value)
@ -1572,6 +1574,14 @@ func (x *ObjectConsExpression) print(w io.Writer, p *printer) {
if tokens.Comma != nil {
p.fprintf(w, "%v", tokens.Comma)
}
if isMultiLine && i == len(items)-1 {
trailingTrivia := item.Value.GetTrailingTrivia()
if tokens.Comma != nil {
trailingTrivia = tokens.Comma.TrailingTrivia
}
trailingNewline = trailingTrivia.EndsOnNewLine()
}
}
if len(x.Items) < len(items) {
@ -1585,7 +1595,11 @@ func (x *ObjectConsExpression) print(w io.Writer, p *printer) {
})
if x.Tokens != nil {
p.fprintf(w, "%v%)", x.Tokens.CloseBrace, x.Tokens.Parentheses)
pre := ""
if isMultiLine && !trailingNewline {
pre = "\n" + p.indent
}
p.fprintf(w, "%s%v%)", pre, x.Tokens.CloseBrace, x.Tokens.Parentheses)
} else {
p.fprintf(w, "\n%s}", p.indent)
}

View file

@ -0,0 +1,26 @@
package model
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zclconf/go-cty/cty"
)
func TestPrintNoTokens(t *testing.T) {
b := &Block{
Type: "block", Body: &Body{
Items: []BodyItem{
&Attribute{
Name: "attribute",
Value: &LiteralValueExpression{
Value: cty.True,
},
},
},
},
}
expected := "block {\n attribute = true\n}"
assert.Equal(t, expected, fmt.Sprintf("%v", b))
}

View file

@ -19,6 +19,8 @@ import (
"io"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/syntax"
)
type printable interface {
@ -28,6 +30,10 @@ type printable interface {
HasLeadingTrivia() bool
// HasTrailingTrivia returns true if the value has associated trailing trivia.
HasTrailingTrivia() bool
// GetLeadingTrivia returns the leading trivia for this value, if any.
GetLeadingTrivia() syntax.TriviaList
// GetTrailingTrivia returns the trailing trivia for this value, if any.
GetTrailingTrivia() syntax.TriviaList
}
type printer struct {
@ -50,7 +56,7 @@ func (p *printer) format(f fmt.State, c rune, pp printable) {
if f.Flag(' ') && !pp.HasLeadingTrivia() {
switch pp.(type) {
case BodyItem:
p.fprintf(f, "\n%s", p.indent)
p.fprintf(f, "%s", p.indent)
case Expression:
p.fprintf(f, " ")
}

View file

@ -150,6 +150,10 @@ func (r *applyRewriter) inspectsEventualValues(x model.Expression) bool {
case *model.ForExpression:
return r.hasEventualElements(x.Collection)
case *model.FunctionCallExpression:
_, isEventual := r.isEventualType(x.Signature.ReturnType)
if isEventual {
return true
}
for i, arg := range x.Args {
if r.hasEventualValues(arg) && r.isPromptArg(x.Signature.Parameters[i].Type, arg) {
return true
@ -385,8 +389,18 @@ func (ctx *observeContext) rewriteApplyArg(applyArg model.Expression, paramType
func (ctx *observeContext) rewriteRelativeTraversalExpression(expr *model.RelativeTraversalExpression,
isRoot bool) model.Expression {
// If the access is not an output() or a promise(), return the node as-is.
paramType, isEventual := ctx.isEventualType(expr.Type())
if !isEventual {
return expr
}
// If the receiver is an eventual type, we're done.
if receiverResolvedType, isEventual := ctx.isEventualType(model.GetTraversableType(expr.Parts[0])); isEventual {
return ctx.rewriteApplyArg(expr.Source, receiverResolvedType, expr.Traversal, expr.Parts[1:], isRoot)
}
// Compute the type of the apply and callback arguments.
paramType := model.ResolveOutputs(expr.Type())
parts, traversal := expr.Parts, expr.Traversal
for i := range expr.Traversal {
partResolvedType, isEventual := paramType, true

View file

@ -20,6 +20,7 @@ func (nameInfo) Format(name string) string {
func TestApplyRewriter(t *testing.T) {
cases := []struct {
input, output string
skipPromises bool
}{
{
input: `"v: ${resource.foo.bar}"`,
@ -79,23 +80,45 @@ func TestApplyRewriter(t *testing.T) {
},
{
input: `toJSON({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = "*"
Action = [ "s3:GetObject" ]
Resource = [ "arn:aws:s3:::${resource.id}/*" ]
}]
})`,
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = "*"
Action = [ "s3:GetObject" ]
Resource = [ "arn:aws:s3:::${resource.id}/*" ]
}]
})`,
output: `__apply(resource.id,eval(id, toJSON({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = "*"
Action = [ "s3:GetObject" ]
Resource = [ "arn:aws:s3:::${id}/*" ]
}]
})))`,
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = "*"
Action = [ "s3:GetObject" ]
Resource = [ "arn:aws:s3:::${id}/*" ]
}]
})))`,
},
{
input: `getPromise().property`,
output: `__apply(getPromise(), eval(getPromise, getPromise.property))`,
},
{
input: `getPromise().object.foo`,
output: `__apply(getPromise(), eval(getPromise, getPromise.object.foo))`,
},
{
input: `getPromise().property`,
output: `getPromise().property`,
skipPromises: true,
},
{
input: `getPromise().object.foo`,
output: `getPromise().object.foo`,
skipPromises: true,
},
{
input: `getPromise(resource.id).property`,
output: `__apply(__apply(resource.id,eval(id, getPromise(id))), eval(getPromise, getPromise.property))`,
},
}
@ -130,14 +153,28 @@ func TestApplyRewriter(t *testing.T) {
})
scope.DefineFunction("element", pulumiBuiltins["element"])
scope.DefineFunction("toJSON", pulumiBuiltins["toJSON"])
scope.DefineFunction("getPromise", model.NewFunction(model.StaticFunctionSignature{
Parameters: []model.Parameter{{
Name: "p",
Type: model.NewOptionalType(model.StringType),
}},
ReturnType: model.NewPromiseType(model.NewObjectType(map[string]model.Type{
"property": model.StringType,
"object": model.NewObjectType(map[string]model.Type{
"foo": model.StringType,
}),
})),
}))
for _, c := range cases {
expr, diags := model.BindExpressionText(c.input, scope, hcl.Pos{})
assert.Len(t, diags, 0)
t.Run(c.input, func(t *testing.T) {
expr, diags := model.BindExpressionText(c.input, scope, hcl.Pos{})
assert.Len(t, diags, 0)
expr, diags = RewriteApplies(expr, nameInfo(0), true)
assert.Len(t, diags, 0)
expr, diags = RewriteApplies(expr, nameInfo(0), !c.skipPromises)
assert.Len(t, diags, 0)
assert.Equal(t, c.output, fmt.Sprintf("%v", expr))
assert.Equal(t, c.output, fmt.Sprintf("%v", expr))
})
}
}

View file

@ -1,8 +1,11 @@
package syntax
import (
"bytes"
"fmt"
"math/big"
"unicode"
"unicode/utf8"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
@ -110,6 +113,33 @@ func (trivia TriviaList) CollapseWhitespace() TriviaList {
return result
}
func (trivia TriviaList) EndsOnNewLine() bool {
for _, trivia := range trivia {
b := trivia.Bytes()
for len(b) > 0 {
r, sz := utf8.DecodeLastRune(b)
if r == '\n' {
return true
}
if !unicode.IsSpace(r) {
return false
}
b = b[:len(b)-sz]
}
}
return false
}
func (trivia TriviaList) Index(sep string) (Trivia, int) {
s := []byte(sep)
for _, trivia := range trivia {
if i := bytes.Index(trivia.Bytes(), s); i != -1 {
return trivia, i
}
}
return nil, -1
}
func (trivia TriviaList) Format(f fmt.State, c rune) {
for _, trivia := range trivia {
_, err := f.Write(trivia.Bytes())
@ -173,6 +203,26 @@ type TemplateDelimiter struct {
bytes []byte
}
// NewTemplateDelimiter creates a new TemplateDelimiter value with the given delimiter type. If the token type is not a
// template delimiter, this function will panic.
func NewTemplateDelimiter(typ hclsyntax.TokenType) TemplateDelimiter {
var s string
switch typ {
case hclsyntax.TokenTemplateInterp:
s = "${"
case hclsyntax.TokenTemplateControl:
s = "%{"
case hclsyntax.TokenTemplateSeqEnd:
s = "}"
default:
panic(fmt.Errorf("%v is not a template delimiter", typ))
}
return TemplateDelimiter{
Type: typ,
bytes: []byte(s),
}
}
// Range returns the range of the delimiter in the source file.
func (t TemplateDelimiter) Range() hcl.Range {
return t.rng

View file

@ -0,0 +1,468 @@
// Copyright 2016-2020, 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 importer
import (
"fmt"
"math"
"strings"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/syntax"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/pkg/v2/resource/deploy/providers"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/zclconf/go-cty/cty"
)
// Null represents Pulumi HCL2's `null` variable.
var Null = &model.Variable{
Name: "null",
VariableType: model.NoneType,
}
// GenerateHCL2Definition generates a Pulumi HCL2 definition for a given resource.
func GenerateHCL2Definition(loader schema.Loader, state *resource.State, names NameTable) (*model.Block, error) {
// TODO: pull the package version from the resource's provider
pkg, err := loader.LoadPackage(string(state.Type.Package()), nil)
if err != nil {
return nil, err
}
r, ok := pkg.GetResource(string(state.Type))
if !ok {
return nil, fmt.Errorf("unknown resource type '%v'", r)
}
var items []model.BodyItem
for _, p := range r.InputProperties {
x, err := generatePropertyValue(p, state.Inputs[resource.PropertyKey(p.Name)])
if err != nil {
return nil, err
}
if x != nil {
items = append(items, &model.Attribute{
Name: p.Name,
Value: x,
})
}
}
resourceOptions, err := makeResourceOptions(state, names)
if err != nil {
return nil, err
}
if resourceOptions != nil {
items = append(items, resourceOptions)
}
typ, name := state.URN.Type(), state.URN.Name()
return &model.Block{
Tokens: syntax.NewBlockTokens("resource", string(name), string(typ)),
Type: "resource",
Labels: []string{string(name), string(typ)},
Body: &model.Body{
Items: items,
},
}, nil
}
func newVariableReference(name string) model.Expression {
return model.VariableReference(&model.Variable{
Name: name,
VariableType: model.DynamicType,
})
}
func appendResourceOption(block *model.Block, name string, value model.Expression) *model.Block {
if block == nil {
block = &model.Block{
Tokens: syntax.NewBlockTokens("options"),
Type: "options",
Body: &model.Body{},
}
}
block.Body.Items = append(block.Body.Items, &model.Attribute{
Tokens: syntax.NewAttributeTokens(name),
Name: name,
Value: value,
})
return block
}
func makeResourceOptions(state *resource.State, names NameTable) (*model.Block, error) {
var resourceOptions *model.Block
if state.Parent != "" && state.Parent.Type() != resource.RootStackType {
name, ok := names[state.Parent]
if !ok {
return nil, fmt.Errorf("no name for parent %v", state.Parent)
}
resourceOptions = appendResourceOption(resourceOptions, "parent", newVariableReference(name))
}
if state.Provider != "" {
ref, err := providers.ParseReference(state.Provider)
if err != nil {
return nil, fmt.Errorf("invalid provider reference %v: %w", state.Provider, err)
}
if !providers.IsDefaultProvider(ref.URN()) {
name, ok := names[ref.URN()]
if !ok {
return nil, fmt.Errorf("no name for provider %v", state.Parent)
}
resourceOptions = appendResourceOption(resourceOptions, "provider", newVariableReference(name))
}
}
if len(state.Dependencies) != 0 {
deps := make([]model.Expression, len(state.Dependencies))
for i, d := range state.Dependencies {
name, ok := names[d]
if !ok {
return nil, fmt.Errorf("no name for resource %v", d)
}
deps[i] = newVariableReference(name)
}
resourceOptions = appendResourceOption(resourceOptions, "dependsOn", &model.TupleConsExpression{
Tokens: syntax.NewTupleConsTokens(len(deps)),
Expressions: deps,
})
}
if state.Protect {
resourceOptions = appendResourceOption(resourceOptions, "protect", &model.LiteralValueExpression{
Tokens: syntax.NewLiteralValueTokens(cty.True),
Value: cty.True,
})
}
return resourceOptions, nil
}
// typeRank orders types by their simplicity.
func typeRank(t schema.Type) int {
switch t {
case schema.BoolType:
return 1
case schema.IntType:
return 2
case schema.NumberType:
return 3
case schema.StringType:
return 4
case schema.AssetType:
return 5
case schema.ArchiveType:
return 6
case schema.JSONType:
return 7
case schema.AnyType:
return 13
default:
switch t.(type) {
case *schema.TokenType:
return 8
case *schema.ArrayType:
return 9
case *schema.MapType:
return 10
case *schema.ObjectType:
return 11
case *schema.UnionType:
return 12
default:
return int(math.MaxInt32)
}
}
}
// simplerType returns true if T is simpler than U.
//
// The first-order ranking is:
//
// bool < int < number < string < archive < asset < json < token < array < map < object < union < any
//
// Additional rules apply to composite types of the same kind:
// - array(T) is simpler than array(U) if T is simpler than U
// - map(T) is simpler than map(U) if T is simpler than U
// - object({ ... }) is simpler than object({ ... }) if the former has a greater number of required properties that are
// simpler than the latter's required properties
// - union(...) is simpler than union(...) if the former's simplest element type is simpler than the latter's simplest
// element type
func simplerType(t, u schema.Type) bool {
tRank, uRank := typeRank(t), typeRank(u)
if tRank < uRank {
return true
} else if tRank > uRank {
return false
}
// At this point we know that t and u have the same concrete type.
switch t := t.(type) {
case *schema.TokenType:
u := u.(*schema.TokenType)
if t.UnderlyingType != nil && u.UnderlyingType != nil {
return simplerType(t.UnderlyingType, u.UnderlyingType)
}
return false
case *schema.ArrayType:
return simplerType(t.ElementType, u.(*schema.ArrayType).ElementType)
case *schema.MapType:
return simplerType(t.ElementType, u.(*schema.MapType).ElementType)
case *schema.ObjectType:
// Count how many of T's required properties are simpler than U's required properties and vice versa.
uu := u.(*schema.ObjectType)
tscore, nt, uscore := 0, 0, 0
for _, p := range t.Properties {
if p.IsRequired {
nt++
for _, q := range uu.Properties {
if q.IsRequired {
if simplerType(p.Type, q.Type) {
tscore++
}
if simplerType(q.Type, p.Type) {
uscore++
}
}
}
}
}
// If the number of T's required properties that are simpler that U's required properties exceeds the number
// of U's required properties that are simpler than T's required properties, T is simpler.
if tscore > uscore {
return true
}
if tscore < uscore {
return false
}
// If the above counts are equal, T is simpler if it has fewer required properties.
nu := 0
for _, q := range uu.Properties {
if q.IsRequired {
nu++
}
}
return nt < nu
case *schema.UnionType:
// Pick whichever has the simplest element type.
var simplestElementType schema.Type
for _, u := range u.(*schema.UnionType).ElementTypes {
if simplestElementType == nil || simplerType(u, simplestElementType) {
simplestElementType = u
}
}
for _, t := range t.ElementTypes {
if simplestElementType == nil || simplerType(t, simplestElementType) {
return true
}
}
return false
default:
return false
}
}
// zeroValue constructs a zero value of the given type.
func zeroValue(t schema.Type) model.Expression {
switch t := t.(type) {
case *schema.MapType:
return &model.ObjectConsExpression{}
case *schema.ArrayType:
return &model.TupleConsExpression{}
case *schema.UnionType:
// If there is a default type, create a value of that type.
if t.DefaultType != nil {
return zeroValue(t.DefaultType)
}
// Otherwise, pick the simplest type in the list.
var simplestType schema.Type
for _, t := range t.ElementTypes {
if simplestType == nil || simplerType(t, simplestType) {
simplestType = t
}
}
return zeroValue(simplestType)
case *schema.ObjectType:
var items []model.ObjectConsItem
for _, p := range t.Properties {
if p.IsRequired {
items = append(items, model.ObjectConsItem{
Key: &model.LiteralValueExpression{
Value: cty.StringVal(p.Name),
},
Value: zeroValue(p.Type),
})
}
}
return &model.ObjectConsExpression{Items: items}
case *schema.TokenType:
if t.UnderlyingType != nil {
return zeroValue(t.UnderlyingType)
}
return model.VariableReference(Null)
}
switch t {
case schema.BoolType:
x, err := generateValue(t, resource.NewBoolProperty(false))
contract.IgnoreError(err)
return x
case schema.IntType, schema.NumberType:
x, err := generateValue(t, resource.NewNumberProperty(0))
contract.IgnoreError(err)
return x
case schema.StringType:
x, err := generateValue(t, resource.NewStringProperty(""))
contract.IgnoreError(err)
return x
case schema.ArchiveType, schema.AssetType:
return model.VariableReference(Null)
case schema.JSONType, schema.AnyType:
return &model.ObjectConsExpression{}
default:
contract.Failf("unexpected schema type %v", t)
return nil
}
}
// generatePropertyValue generates the value for the given property. If the value is absent and the property is
// required, a zero value for the property's type is generated. If the value is absent and the property is not
// required, no value is generated (i.e. this function returns nil).
func generatePropertyValue(property *schema.Property, value resource.PropertyValue) (model.Expression, error) {
if !value.HasValue() {
if !property.IsRequired {
return nil, nil
}
return zeroValue(property.Type), nil
}
return generateValue(property.Type, value)
}
// generateValue generates a value from the given property value. The given type may or may not match the shape of the
// given value.
func generateValue(typ schema.Type, value resource.PropertyValue) (model.Expression, error) {
switch {
case value.IsArchive():
return nil, fmt.Errorf("NYI: archives")
case value.IsArray():
elementType := schema.AnyType
if typ, ok := typ.(*schema.ArrayType); ok {
elementType = typ.ElementType
}
arr := value.ArrayValue()
exprs := make([]model.Expression, len(arr))
for i, v := range arr {
x, err := generateValue(elementType, v)
if err != nil {
return nil, err
}
exprs[i] = x
}
return &model.TupleConsExpression{
Tokens: syntax.NewTupleConsTokens(len(exprs)),
Expressions: exprs,
}, nil
case value.IsAsset():
return nil, fmt.Errorf("NYI: assets")
case value.IsBool():
return &model.LiteralValueExpression{
Value: cty.BoolVal(value.BoolValue()),
}, nil
case value.IsComputed() || value.IsOutput():
return nil, fmt.Errorf("cannot define computed values")
case value.IsNull():
return model.VariableReference(Null), nil
case value.IsNumber():
return &model.LiteralValueExpression{
Value: cty.NumberFloatVal(value.NumberValue()),
}, nil
case value.IsObject():
obj := value.ObjectValue()
items := make([]model.ObjectConsItem, 0, len(obj))
if objectType, ok := typ.(*schema.ObjectType); ok {
for _, p := range objectType.Properties {
x, err := generatePropertyValue(p, obj[resource.PropertyKey(p.Name)])
if err != nil {
return nil, err
}
if x != nil {
items = append(items, model.ObjectConsItem{
Key: &model.LiteralValueExpression{
Value: cty.StringVal(p.Name),
},
Value: x,
})
}
}
} else {
elementType := schema.AnyType
if mapType, ok := typ.(*schema.MapType); ok {
elementType = mapType.ElementType
}
for _, k := range obj.StableKeys() {
// Ignore internal properties.
if strings.HasPrefix(string(k), "__") {
continue
}
x, err := generateValue(elementType, obj[k])
if err != nil {
return nil, err
}
items = append(items, model.ObjectConsItem{
Key: &model.LiteralValueExpression{
Value: cty.StringVal(string(k)),
},
Value: x,
})
}
}
return &model.ObjectConsExpression{
Tokens: syntax.NewObjectConsTokens(len(items)),
Items: items,
}, nil
case value.IsSecret():
arg, err := generateValue(typ, value.SecretValue().Element)
if err != nil {
return nil, err
}
return &model.FunctionCallExpression{
Name: "secret",
Signature: model.StaticFunctionSignature{
Parameters: []model.Parameter{{
Name: "value",
Type: arg.Type(),
}},
ReturnType: model.NewOutputType(arg.Type()),
},
Args: []model.Expression{arg},
}, nil
case value.IsString():
return &model.TemplateExpression{
Parts: []model.Expression{
&model.LiteralValueExpression{
Value: cty.StringVal(value.StringValue()),
},
},
}, nil
default:
contract.Failf("unexpected property value %v", value)
return nil, nil
}
}

View file

@ -0,0 +1,332 @@
// Copyright 2016-2020, 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 importer
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/hashicorp/hcl/v2"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/model"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/syntax"
"github.com/pulumi/pulumi/pkg/v2/codegen/internal/test"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/pkg/v2/resource/deploy/providers"
"github.com/pulumi/pulumi/pkg/v2/resource/stack"
"github.com/pulumi/pulumi/sdk/v2/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/config"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/stretchr/testify/assert"
"github.com/zclconf/go-cty/cty"
)
var testdataPath = filepath.Join("..", "internal", "test", "testdata")
const parentName = "parent"
const providerName = "provider"
var parentURN = resource.NewURN("stack", "project", "", "my::parent", "parent")
var providerURN = resource.NewURN("stack", "project", "", providers.MakeProviderType("pkg"), "provider")
var names = NameTable{
parentURN: parentName,
providerURN: providerName,
}
func renderExpr(t *testing.T, x model.Expression) resource.PropertyValue {
switch x := x.(type) {
case *model.LiteralValueExpression:
return renderLiteralValue(t, x)
case *model.ScopeTraversalExpression:
return renderScopeTraversal(t, x)
case *model.TemplateExpression:
return renderTemplate(t, x)
case *model.TupleConsExpression:
return renderTupleCons(t, x)
case *model.ObjectConsExpression:
return renderObjectCons(t, x)
case *model.FunctionCallExpression:
return renderFunctionCall(t, x)
default:
assert.Failf(t, "", "unexpected expression of type %T", x)
return resource.NewNullProperty()
}
}
func renderLiteralValue(t *testing.T, x *model.LiteralValueExpression) resource.PropertyValue {
switch x.Value.Type() {
case cty.Bool:
return resource.NewBoolProperty(x.Value.True())
case cty.Number:
f, _ := x.Value.AsBigFloat().Float64()
return resource.NewNumberProperty(f)
case cty.String:
return resource.NewStringProperty(x.Value.AsString())
default:
assert.Failf(t, "", "unexpected literal of type %v", x.Value.Type())
return resource.NewNullProperty()
}
}
func renderTemplate(t *testing.T, x *model.TemplateExpression) resource.PropertyValue {
if !assert.Len(t, x.Parts, 1) {
return resource.NewStringProperty("")
}
return renderLiteralValue(t, x.Parts[0].(*model.LiteralValueExpression))
}
func renderObjectCons(t *testing.T, x *model.ObjectConsExpression) resource.PropertyValue {
obj := resource.PropertyMap{}
for _, item := range x.Items {
kv := renderExpr(t, item.Key)
if !assert.True(t, kv.IsString()) {
continue
}
obj[resource.PropertyKey(kv.StringValue())] = renderExpr(t, item.Value)
}
return resource.NewObjectProperty(obj)
}
func renderScopeTraversal(t *testing.T, x *model.ScopeTraversalExpression) resource.PropertyValue {
if !assert.Len(t, x.Traversal, 1) {
return resource.NewNullProperty()
}
switch x.RootName {
case "parent":
return resource.NewStringProperty(string(parentURN))
case "provider":
return resource.NewStringProperty(string(providerURN))
default:
assert.Failf(t, "", "unexpected variable reference %v", x.RootName)
return resource.NewNullProperty()
}
}
func renderTupleCons(t *testing.T, x *model.TupleConsExpression) resource.PropertyValue {
arr := make([]resource.PropertyValue, len(x.Expressions))
for i, x := range x.Expressions {
arr[i] = renderExpr(t, x)
}
return resource.NewArrayProperty(arr)
}
func renderFunctionCall(t *testing.T, x *model.FunctionCallExpression) resource.PropertyValue {
switch x.Name {
case "secret":
if !assert.Len(t, x.Args, 1) {
return resource.NewNullProperty()
}
return resource.MakeSecret(renderExpr(t, x.Args[0]))
default:
assert.Failf(t, "", "unexpected call to %v", x.Name)
return resource.NewNullProperty()
}
}
func renderResource(t *testing.T, r *hcl2.Resource) *resource.State {
inputs := resource.PropertyMap{}
for _, attr := range r.Inputs {
inputs[resource.PropertyKey(attr.Name)] = renderExpr(t, attr.Value)
}
protect := false
var parent resource.URN
var providerRef string
if r.Options != nil {
if r.Options.Protect != nil {
v, diags := r.Options.Protect.Evaluate(&hcl.EvalContext{})
if assert.Len(t, diags, 0) && assert.Equal(t, cty.Bool, v.Type()) {
protect = v.True()
}
}
if r.Options.Parent != nil {
v := renderExpr(t, r.Options.Parent)
if assert.True(t, v.IsString()) {
parent = resource.URN(v.StringValue())
}
}
if r.Options.Provider != nil {
v := renderExpr(t, r.Options.Provider)
if assert.True(t, v.IsString()) {
providerRef = v.StringValue() + "::id"
}
}
}
// Pull the raw token from the resource.
token := tokens.Type(r.Definition.Labels[1])
var parentType tokens.Type
if parent != "" {
parentType = parent.QualifiedType()
}
return &resource.State{
Type: token,
URN: resource.NewURN("stack", "project", parentType, token, tokens.QName(r.Name())),
Custom: true,
Inputs: inputs,
Parent: parent,
Provider: providerRef,
Protect: protect,
}
}
type testCases struct {
Resources []apitype.ResourceV3 `json:"resources"`
}
func readTestCases(path string) (testCases, error) {
f, err := os.Open(path)
if err != nil {
return testCases{}, err
}
defer contract.IgnoreClose(f)
var cases testCases
if err = json.NewDecoder(f).Decode(&cases); err != nil {
return testCases{}, err
}
return cases, nil
}
func TestGenerateHCL2Definition(t *testing.T) {
loader := schema.NewPluginLoader(test.NewHost(testdataPath))
cases, err := readTestCases("testdata/cases.json")
if !assert.NoError(t, err) {
t.Fatal()
}
for _, s := range cases.Resources {
t.Run(string(s.URN), func(t *testing.T) {
state, err := stack.DeserializeResource(s, config.NopDecrypter, config.NopEncrypter)
if !assert.NoError(t, err) {
t.Fatal()
}
block, err := GenerateHCL2Definition(loader, state, names)
if !assert.NoError(t, err) {
t.Fatal()
}
text := fmt.Sprintf("%v", block)
parser := syntax.NewParser()
err = parser.ParseFile(strings.NewReader(text), string(state.URN)+".pp")
if !assert.NoError(t, err) || !assert.False(t, parser.Diagnostics.HasErrors()) {
t.Fatal()
}
p, diags, err := hcl2.BindProgram(parser.Files, hcl2.Loader(loader), hcl2.AllowMissingVariables)
assert.NoError(t, err)
assert.False(t, diags.HasErrors())
if !assert.Len(t, p.Nodes, 1) {
t.Fatal()
}
res, isResource := p.Nodes[0].(*hcl2.Resource)
if !assert.True(t, isResource) {
t.Fatal()
}
actualState := renderResource(t, res)
assert.Equal(t, state.Type, actualState.Type)
assert.Equal(t, state.URN, actualState.URN)
assert.Equal(t, state.Parent, actualState.Parent)
assert.Equal(t, state.Provider, actualState.Provider)
assert.Equal(t, state.Protect, actualState.Protect)
if !assert.True(t, actualState.Inputs.DeepEquals(state.Inputs)) {
actual, err := stack.SerializeResource(actualState, config.NopEncrypter, false)
contract.IgnoreError(err)
sb, err := json.MarshalIndent(s, "", " ")
contract.IgnoreError(err)
ab, err := json.MarshalIndent(actual, "", " ")
contract.IgnoreError(err)
t.Logf("%v\n\n%v\n\n%v\n", text, string(sb), string(ab))
}
})
}
}
func TestSimplerType(t *testing.T) {
types := []schema.Type{
schema.BoolType,
schema.IntType,
schema.NumberType,
schema.StringType,
schema.AssetType,
schema.ArchiveType,
schema.JSONType,
&schema.ArrayType{ElementType: schema.BoolType},
&schema.ArrayType{ElementType: schema.IntType},
&schema.MapType{ElementType: schema.BoolType},
&schema.MapType{ElementType: schema.IntType},
&schema.ObjectType{},
&schema.ObjectType{
Properties: []*schema.Property{
{
Name: "foo",
Type: schema.BoolType,
IsRequired: true,
},
},
},
&schema.ObjectType{
Properties: []*schema.Property{
{
Name: "foo",
Type: schema.IntType,
IsRequired: true,
},
},
},
&schema.ObjectType{
Properties: []*schema.Property{
{
Name: "foo",
Type: schema.IntType,
IsRequired: true,
},
{
Name: "bar",
Type: schema.IntType,
IsRequired: true,
},
},
},
&schema.UnionType{ElementTypes: []schema.Type{schema.BoolType, schema.IntType}},
&schema.UnionType{ElementTypes: []schema.Type{schema.IntType, schema.JSONType}},
&schema.UnionType{ElementTypes: []schema.Type{schema.NumberType, schema.StringType}},
schema.AnyType,
}
assert.True(t, sort.SliceIsSorted(types, func(i, j int) bool {
return simplerType(types[i], types[j])
}))
}

View file

@ -0,0 +1,108 @@
// Copyright 2016-2020, 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 importer
import (
"bytes"
"fmt"
"io"
"github.com/hashicorp/hcl/v2"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2/syntax"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
)
// A LangaugeGenerator generates code for a given Pulumi program to an io.Writer.
type LanguageGenerator func(w io.Writer, p *hcl2.Program) error
// A NameTable maps URNs to language-specific variable names.
type NameTable map[resource.URN]string
// A DiagnosticsError captures HCL2 diagnostics.
type DiagnosticsError struct {
diagnostics hcl.Diagnostics
newDiagnosticWriter func(w io.Writer, width uint, color bool) hcl.DiagnosticWriter
}
func (e *DiagnosticsError) Diagnostics() hcl.Diagnostics {
return e.diagnostics
}
// NewDiagnosticWriter returns an hcl.DiagnosticWriter that can be used to render the error's diagnostics.
func (e *DiagnosticsError) NewDiagnosticWriter(w io.Writer, width uint, color bool) hcl.DiagnosticWriter {
return e.newDiagnosticWriter(w, width, color)
}
func (e *DiagnosticsError) Error() string {
var text bytes.Buffer
err := e.NewDiagnosticWriter(&text, 0, false).WriteDiagnostics(e.diagnostics)
contract.IgnoreError(err)
return text.String()
}
func (e *DiagnosticsError) String() string {
return e.Error()
}
// GenerateLanguageDefintions generates a list of resource definitions from the given resource states.
func GenerateLanguageDefinitions(w io.Writer, loader schema.Loader, gen LanguageGenerator, states []*resource.State,
names NameTable) error {
var hcl2Text bytes.Buffer
for i, state := range states {
hcl2Def, err := GenerateHCL2Definition(loader, state, names)
if err != nil {
return err
}
pre := ""
if i > 0 {
pre = "\n"
}
_, err = fmt.Fprintf(&hcl2Text, "%s%v", pre, hcl2Def)
contract.IgnoreError(err)
}
parser := syntax.NewParser()
if err := parser.ParseFile(&hcl2Text, string("anonymous.pp")); err != nil {
return err
}
if parser.Diagnostics.HasErrors() {
// HCL2 text generation should always generate proper code.
return fmt.Errorf("internal error: %w", &DiagnosticsError{
diagnostics: parser.Diagnostics,
newDiagnosticWriter: parser.NewDiagnosticWriter,
})
}
program, diags, err := hcl2.BindProgram(parser.Files, hcl2.Loader(loader), hcl2.AllowMissingVariables)
if err != nil {
return err
}
if diags.HasErrors() {
// It is possible that the provided states do not contain appropriately-shaped inputs, so this may be user
// error.
return &DiagnosticsError{
diagnostics: diags,
newDiagnosticWriter: program.NewDiagnosticWriter,
}
}
return gen(w, program)
}

View file

@ -0,0 +1,85 @@
// Copyright 2016-2020, 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 importer
import (
"encoding/json"
"io"
"io/ioutil"
"testing"
"github.com/pulumi/pulumi/pkg/v2/codegen/hcl2"
"github.com/pulumi/pulumi/pkg/v2/codegen/internal/test"
"github.com/pulumi/pulumi/pkg/v2/codegen/schema"
"github.com/pulumi/pulumi/pkg/v2/resource/stack"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/config"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/stretchr/testify/assert"
)
func TestGenerateLanguageDefinition(t *testing.T) {
loader := schema.NewPluginLoader(test.NewHost(testdataPath))
cases, err := readTestCases("testdata/cases.json")
if !assert.NoError(t, err) {
t.Fatal()
}
for _, s := range cases.Resources {
t.Run(string(s.URN), func(t *testing.T) {
state, err := stack.DeserializeResource(s, config.NopDecrypter, config.NopEncrypter)
if !assert.NoError(t, err) {
t.Fatal()
}
var actualState *resource.State
err = GenerateLanguageDefinitions(ioutil.Discard, loader, func(_ io.Writer, p *hcl2.Program) error {
if !assert.Len(t, p.Nodes, 1) {
t.Fatal()
}
res, isResource := p.Nodes[0].(*hcl2.Resource)
if !assert.True(t, isResource) {
t.Fatal()
}
actualState = renderResource(t, res)
return nil
}, []*resource.State{state}, names)
if !assert.NoError(t, err) {
t.Fatal()
}
assert.Equal(t, state.Type, actualState.Type)
assert.Equal(t, state.URN, actualState.URN)
assert.Equal(t, state.Parent, actualState.Parent)
assert.Equal(t, state.Provider, actualState.Provider)
assert.Equal(t, state.Protect, actualState.Protect)
if !assert.True(t, actualState.Inputs.DeepEquals(state.Inputs)) {
actual, err := stack.SerializeResource(actualState, config.NopEncrypter, false)
contract.IgnoreError(err)
sb, err := json.MarshalIndent(s, "", " ")
contract.IgnoreError(err)
ab, err := json.MarshalIndent(actual, "", " ")
contract.IgnoreError(err)
t.Logf("%v\n\n%v\n", string(sb), string(ab))
}
})
}
}

1561
pkg/codegen/importer/testdata/cases.json vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -478,9 +478,6 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error {
if r.IsProvider {
trailingBrace, optionsType = " {", "ResourceOptions"
}
if r.StateInputs == nil {
trailingBrace = " {"
}
if r.DeprecationMessage != "" {
fmt.Fprintf(w, " /** @deprecated %s */\n", r.DeprecationMessage)
@ -498,27 +495,34 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error {
// conditional state into sensible variables using dynamic type tests.
fmt.Fprintf(w, " constructor(name: string, argsOrState?: %s | %s, opts?: pulumi.CustomResourceOptions) {\n",
argsType, stateType)
} else {
// Otherwise, write out a constructor with no state and required opts, then another with all optional params.
fmt.Fprintf(w, " constructor(name: string, state: undefined, opts: pulumi.CustomResourceOptions)\n")
fmt.Fprintf(w, " constructor(name: string, argsOrState?: %s, opts?: pulumi.CustomResourceOptions) {\n",
argsType)
}
if r.DeprecationMessage != "" && mod.compatibility != kubernetes20 {
fmt.Fprintf(w, " pulumi.log.warn(\"%s is deprecated: %s\")\n", name, r.DeprecationMessage)
}
fmt.Fprintf(w, " let inputs: pulumi.Inputs = {};\n")
if r.StateInputs != nil {
// The lookup case:
fmt.Fprintf(w, " if (opts && opts.id) {\n")
fmt.Fprintf(w, " const state = argsOrState as %[1]s | undefined;\n", stateType)
for _, prop := range r.Properties {
for _, prop := range r.StateInputs.Properties {
fmt.Fprintf(w, " inputs[\"%[1]s\"] = state ? state.%[1]s : undefined;\n", prop.Name)
}
// The creation case (with args):
fmt.Fprintf(w, " } else {\n")
fmt.Fprintf(w, " const args = argsOrState as %s | undefined;\n", argsType)
} else {
// The creation case:
fmt.Fprintf(w, " if (!(opts && opts.id)) {\n")
}
fmt.Fprintf(w, " const args = argsOrState as %s | undefined;\n", argsType)
} else {
fmt.Fprintf(w, " let inputs: pulumi.Inputs = {};\n")
if r.StateInputs != nil {
fmt.Fprintf(w, " {\n")
}
fmt.Fprintf(w, " {\n")
}
for _, prop := range r.InputProperties {
if prop.IsRequired {
@ -531,10 +535,6 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error {
arg := fmt.Sprintf("args ? args.%[1]s : undefined", prop.Name)
prefix := " "
if r.StateInputs == nil {
prefix = " "
}
if prop.ConstValue != nil {
cv, err := mod.getConstValue(prop.ConstValue)
if err != nil {
@ -566,9 +566,6 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error {
var secretProps []string
for _, prop := range r.Properties {
prefix := " "
if r.StateInputs == nil {
prefix = " "
}
if !ins.Has(prop.Name) {
fmt.Fprintf(w, "%sinputs[\"%s\"] = undefined /*out*/;\n", prefix, prop.Name)
}
@ -577,9 +574,7 @@ func (mod *modContext) genResource(w io.Writer, r *schema.Resource) error {
secretProps = append(secretProps, prop.Name)
}
}
if r.StateInputs != nil {
fmt.Fprintf(w, " }\n")
}
fmt.Fprintf(w, " }\n")
// If the caller didn't request a specific version, supply one using the version of this library.
fmt.Fprintf(w, " if (!opts) {\n")
@ -1059,8 +1054,10 @@ func (mod *modContext) isReservedSourceFileName(name string) bool {
func (mod *modContext) gen(fs fs) error {
files := append([]string(nil), mod.extraSourceFiles...)
modDir := strings.ToLower(mod.mod)
addFile := func(name, contents string) {
p := path.Join(mod.mod, name)
p := path.Join(modDir, name)
files = append(files, p)
fs.add(p, []byte(contents))
}
@ -1071,7 +1068,7 @@ func (mod *modContext) gen(fs fs) error {
buffer := &bytes.Buffer{}
mod.genHeader(buffer, nil, nil)
fmt.Fprintf(buffer, "%s", utilitiesFile)
fs.add(path.Join(mod.mod, "utilities.ts"), buffer.Bytes())
fs.add(path.Join(modDir, "utilities.ts"), buffer.Bytes())
// Ensure that the top-level (provider) module directory contains a README.md file.
readme := mod.pkg.Language["nodejs"].(NodePackageInfo).Readme
@ -1090,7 +1087,7 @@ func (mod *modContext) gen(fs fs) error {
if readme != "" && readme[len(readme)-1] != '\n' {
readme += "\n"
}
fs.add(path.Join(mod.mod, "README.md"), []byte(readme))
fs.add(path.Join(modDir, "README.md"), []byte(readme))
case "config":
if len(mod.pkg.Config) > 0 {
buffer := &bytes.Buffer{}
@ -1140,12 +1137,12 @@ func (mod *modContext) gen(fs fs) error {
// Nested types
if len(mod.types) > 0 {
input, output := mod.genTypes()
fs.add(path.Join(mod.mod, "input.ts"), []byte(input))
fs.add(path.Join(mod.mod, "output.ts"), []byte(output))
fs.add(path.Join(modDir, "input.ts"), []byte(input))
fs.add(path.Join(modDir, "output.ts"), []byte(output))
}
// Index
fs.add(path.Join(mod.mod, "index.ts"), []byte(mod.genIndex(files)))
fs.add(path.Join(modDir, "index.ts"), []byte(mod.genIndex(files)))
return nil
}
@ -1156,10 +1153,11 @@ func (mod *modContext) genIndex(exports []string) string {
// Export anything flatly that is a direct export rather than sub-module.
if len(exports) > 0 {
modDir := strings.ToLower(mod.mod)
fmt.Fprintf(w, "// Export members:\n")
sort.Strings(exports)
for _, exp := range exports {
rel, err := filepath.Rel(mod.mod, exp)
rel, err := filepath.Rel(modDir, exp)
contract.Assert(err == nil)
if path.Base(rel) == "." {
rel = path.Dir(rel)
@ -1171,7 +1169,7 @@ func (mod *modContext) genIndex(exports []string) string {
children := codegen.NewStringSet()
for _, mod := range mod.children {
child := mod.mod
child := strings.ToLower(mod.mod)
if mod.compatibility == kubernetes20 {
// Extract version suffix from child modules. Nested versions will have their own index.ts file.
// Example: apps/v1beta1 -> v1beta1
@ -1238,7 +1236,8 @@ type npmPackage struct {
}
type npmPulumiManifest struct {
Resource bool `json:"resource,omitempty"`
Resource bool `json:"resource,omitempty"`
PluginDownloadURL string `json:"pluginDownloadURL,omitempty"`
}
func genNPMPackageMetadata(pkg *schema.Package, info NodePackageInfo) string {
@ -1270,7 +1269,8 @@ func genNPMPackageMetadata(pkg *schema.Package, info NodePackageInfo) string {
},
DevDependencies: devDependencies,
Pulumi: npmPulumiManifest{
Resource: true,
Resource: true,
PluginDownloadURL: pkg.PluginDownloadURL,
},
}

View file

@ -39,22 +39,28 @@ func (d DocLanguageHelper) GetDocLinkForPulumiType(pkg *schema.Package, typeName
// GetDocLinkForResourceType returns the Python API doc for a type belonging to a resource provider.
func (d DocLanguageHelper) GetDocLinkForResourceType(pkg *schema.Package, modName, typeName string) string {
var path string
// The k8s module names contain the domain names. For now we are stripping them off manually so they link correctly.
if modName != "" {
modName = strings.ReplaceAll(modName, ".k8s.io", "")
modName = strings.ReplaceAll(modName, ".apiserver", "")
modName = strings.ReplaceAll(modName, ".authorization", "")
}
var path string
var fqdnTypeName string
switch {
case pkg.Name != "" && modName != "":
path = fmt.Sprintf("pulumi_%s/%s", pkg.Name, modName)
fqdnTypeName = fmt.Sprintf("pulumi_%s.%s.%s", pkg.Name, modName, typeName)
case pkg.Name == "" && modName != "":
path = modName
fqdnTypeName = fmt.Sprintf("%s.%s", modName, typeName)
case pkg.Name != "" && modName == "":
path = pkg.Name
path = fmt.Sprintf("pulumi_%s", pkg.Name)
fqdnTypeName = fmt.Sprintf("pulumi_%s.%s", pkg.Name, typeName)
}
return fmt.Sprintf("/docs/reference/pkg/python/%s/#%s", path, typeName)
return fmt.Sprintf("/docs/reference/pkg/python/%s/#%s", path, fqdnTypeName)
}
// GetDocLinkForResourceInputOrOutputType is not implemented at this time for Python.

View file

@ -128,7 +128,7 @@ func (mod *modContext) genHeader(w io.Writer, needsSDK bool, needsJSON bool) {
fmt.Fprintf(w, "import pulumi\n")
fmt.Fprintf(w, "import pulumi.runtime\n")
fmt.Fprintf(w, "from typing import Union\n")
fmt.Fprintf(w, "from %s import utilities, tables\n", relImport)
fmt.Fprintf(w, "from %s import _utilities, _tables\n", relImport)
fmt.Fprintf(w, "\n")
}
}
@ -184,7 +184,8 @@ func (mod *modContext) gen(fs fs) error {
buffer := &bytes.Buffer{}
mod.genHeader(buffer, false, false)
fmt.Fprintf(buffer, "%s", utilitiesFile)
fs.add(filepath.Join(dir, "utilities.py"), buffer.Bytes())
fs.add(filepath.Join(dir, "_utilities.py"), buffer.Bytes())
fs.add(filepath.Join(dir, "py.typed"), []byte{})
// Ensure that the top-level (provider) module directory contains a README.md file.
readme := mod.pkg.Language["python"].(PackageInfo).Readme
@ -248,13 +249,7 @@ func (mod *modContext) gen(fs fs) error {
}
func (mod *modContext) submodulesExist() bool {
if len(mod.children) <= 0 {
return false
}
if len(mod.children) == 1 && mod.children[0].mod == "config" {
return false
}
return true
return len(mod.children) > 0
}
// genInit emits an __init__.py module, optionally re-exporting other members or submodules.
@ -262,10 +257,6 @@ func (mod *modContext) genInit(exports []string) string {
w := &bytes.Buffer{}
mod.genHeader(w, false, false)
if mod.submodulesExist() {
fmt.Fprintf(w, "import importlib\n")
}
// Import anything to export flatly that is a direct export rather than sub-module.
if len(exports) > 0 {
sort.Slice(exports, func(i, j int) bool {
@ -291,8 +282,8 @@ func (mod *modContext) genInit(exports []string) string {
})
fmt.Fprintf(w, "\n# Make subpackages available:\n")
fmt.Fprintf(w, "_submodules = [\n")
for i, mod := range mod.children {
fmt.Fprintf(w, "from . import (\n")
for _, mod := range mod.children {
child := mod.mod
if mod.compatibility == kubernetes20 {
// Extract version suffix from child modules. Nested versions will have their own __init__.py file.
@ -301,15 +292,9 @@ func (mod *modContext) genInit(exports []string) string {
child = child[match[2]:match[3]]
}
}
if i > 0 {
fmt.Fprintf(w, ",\n")
}
fmt.Fprintf(w, " '%s'", PyName(child))
fmt.Fprintf(w, " %s,\n", PyName(child))
}
fmt.Fprintf(w, ",\n]\n")
fmt.Fprintf(w, "for pkg in _submodules:\n")
fmt.Fprintf(w, " if pkg != 'config':\n")
fmt.Fprintf(w, " importlib.import_module(f'{__name__}.{pkg}')\n")
fmt.Fprintf(w, ")\n")
}
return w.String()
@ -347,6 +332,7 @@ func (mod *modContext) genAwaitableType(w io.Writer, obj *schema.ObjectType) str
baseName := pyClassName(tokenToName(obj.Token))
// Produce a class definition with optional """ comment.
fmt.Fprint(w, "\n")
fmt.Fprintf(w, "class %s:\n", baseName)
printComment(w, obj.Comment, " ")
@ -378,6 +364,7 @@ func (mod *modContext) genAwaitableType(w io.Writer, obj *schema.ObjectType) str
awaitableName := "Awaitable" + baseName
// Produce an awaitable subclass.
fmt.Fprint(w, "\n\n")
fmt.Fprintf(w, "class %s(%s):\n", awaitableName, baseName)
// Emit __await__ and __iter__ in order to make this type awaitable.
@ -438,7 +425,8 @@ func (mod *modContext) genResource(res *schema.Resource) (string, error) {
}
// Produce a class definition with optional """ comment.
fmt.Fprintf(w, "\nclass %s(%s):\n", name, baseType)
fmt.Fprint(w, "\n")
fmt.Fprintf(w, "class %s(%s):\n", name, baseType)
for _, prop := range res.Properties {
name := PyName(prop.Name)
ty := pyType(prop.Type)
@ -493,7 +481,7 @@ func (mod *modContext) genResource(res *schema.Resource) (string, error) {
fmt.Fprintf(w, " if not isinstance(opts, pulumi.ResourceOptions):\n")
fmt.Fprintf(w, " raise TypeError('Expected resource options to be a ResourceOptions instance')\n")
fmt.Fprintf(w, " if opts.version is None:\n")
fmt.Fprintf(w, " opts.version = utilities.get_version()\n")
fmt.Fprintf(w, " opts.version = _utilities.get_version()\n")
fmt.Fprintf(w, " if opts.id is None:\n")
fmt.Fprintf(w, " if __props__ is not None:\n")
fmt.Fprintf(w, " raise TypeError(")
@ -637,10 +625,10 @@ func (mod *modContext) genResource(res *schema.Resource) (string, error) {
// camel case when interacting with tfbridge.
fmt.Fprintf(w,
` def translate_output_property(self, prop):
return tables._CAMEL_TO_SNAKE_CASE_TABLE.get(prop) or prop
return _tables.CAMEL_TO_SNAKE_CASE_TABLE.get(prop) or prop
def translate_input_property(self, prop):
return tables._SNAKE_TO_CAMEL_CASE_TABLE.get(prop) or prop
return _tables.SNAKE_TO_CAMEL_CASE_TABLE.get(prop) or prop
`)
return w.String(), nil
@ -693,9 +681,10 @@ func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
}
// Write out the function signature.
fmt.Fprint(w, "\n")
fmt.Fprintf(w, "def %s(", name)
for _, arg := range args {
fmt.Fprintf(w, "%s=None,", PyName(arg.Name))
fmt.Fprintf(w, "%s=None, ", PyName(arg.Name))
}
fmt.Fprintf(w, "opts=None")
fmt.Fprintf(w, "):\n")
@ -723,8 +712,7 @@ func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
}
// Copy the function arguments into a dictionary.
fmt.Fprintf(w, " __args__ = dict()\n\n")
fmt.Fprintf(w, "\n")
fmt.Fprintf(w, " __args__ = dict()\n")
for _, arg := range args {
// TODO: args validation.
fmt.Fprintf(w, " __args__['%s'] = %s\n", arg.Name, PyName(arg.Name))
@ -734,7 +722,7 @@ func (mod *modContext) genFunction(fun *schema.Function) (string, error) {
fmt.Fprintf(w, " if opts is None:\n")
fmt.Fprintf(w, " opts = pulumi.InvokeOptions()\n")
fmt.Fprintf(w, " if opts.version is None:\n")
fmt.Fprintf(w, " opts.version = utilities.get_version()\n")
fmt.Fprintf(w, " opts.version = _utilities.get_version()\n")
// Now simply invoke the runtime function with the arguments.
fmt.Fprintf(w, " __ret__ = pulumi.runtime.invoke('%s', __args__, opts=opts).value\n", fun.Token)
@ -793,7 +781,11 @@ func genPackageMetadata(tool string, pkg *schema.Package, requires map[string]st
fmt.Fprintf(w, " def run(self):\n")
fmt.Fprintf(w, " install.run(self)\n")
fmt.Fprintf(w, " try:\n")
fmt.Fprintf(w, " check_call(['pulumi', 'plugin', 'install', 'resource', '%s', '${PLUGIN_VERSION}'])\n", pkg.Name)
if pkg.PluginDownloadURL == "" {
fmt.Fprintf(w, " check_call(['pulumi', 'plugin', 'install', 'resource', '%s', '${PLUGIN_VERSION}'])\n", pkg.Name)
} else {
fmt.Fprintf(w, " check_call(['pulumi', 'plugin', 'install', 'resource', '%s', '${PLUGIN_VERSION}', '--server', '%s'])\n", pkg.Name, pkg.PluginDownloadURL)
}
fmt.Fprintf(w, " except OSError as error:\n")
fmt.Fprintf(w, " if error.errno == errno.ENOENT:\n")
fmt.Fprintf(w, " print(\"\"\"\n")
@ -933,7 +925,7 @@ func (mod *modContext) genPropertyConversionTables() string {
}
sort.Strings(allKeys)
fmt.Fprintf(w, "_SNAKE_TO_CAMEL_CASE_TABLE = {\n")
fmt.Fprintf(w, "SNAKE_TO_CAMEL_CASE_TABLE = {\n")
for _, key := range allKeys {
value := mod.snakeCaseToCamelCase[key]
if key != value {
@ -941,7 +933,7 @@ func (mod *modContext) genPropertyConversionTables() string {
}
}
fmt.Fprintf(w, "}\n")
fmt.Fprintf(w, "\n_CAMEL_TO_SNAKE_CASE_TABLE = {\n")
fmt.Fprintf(w, "\nCAMEL_TO_SNAKE_CASE_TABLE = {\n")
for _, value := range allKeys {
key := mod.snakeCaseToCamelCase[value]
if key != value {
@ -1297,14 +1289,14 @@ func getDefaultValue(dv *schema.DefaultValue, t schema.Type) (string, error) {
}
if len(dv.Environment) > 0 {
envFunc := "utilities.get_env"
envFunc := "_utilities.get_env"
switch t {
case schema.BoolType:
envFunc = "utilities.get_env_bool"
envFunc = "_utilities.get_env_bool"
case schema.IntType:
envFunc = "utilities.get_env_int"
envFunc = "_utilities.get_env_int"
case schema.NumberType:
envFunc = "utilities.get_env_float"
envFunc = "_utilities.get_env_float"
}
envVars := fmt.Sprintf("'%s'", dv.Environment[0])
@ -1360,7 +1352,7 @@ func generateModuleContextMap(tool string, pkg *schema.Package, info PackageInfo
getModFromToken := func(token string) *modContext {
canonicalModName := pkg.TokenToModule(token)
modName := PyName(canonicalModName)
modName := PyName(strings.ToLower(canonicalModName))
if override, ok := info.ModuleNameOverrides[canonicalModName]; ok {
modName = override
}
@ -1475,7 +1467,7 @@ func GeneratePackage(tool string, pkg *schema.Package, extraFiles map[string][]b
}
// Emit casing tables.
files.add(filepath.Join(pyPack(pkg.Name), "tables.py"), []byte(modules[""].genPropertyConversionTables()))
files.add(filepath.Join(pyPack(pkg.Name), "_tables.py"), []byte(modules[""].genPropertyConversionTables()))
// Finally emit the package metadata (setup.py).
setup, err := genPackageMetadata(tool, pkg, info.Requires)
@ -1537,7 +1529,7 @@ def get_env_float(*args):
def get_version():
# __name__ is set to the fully-qualified name of the current module, In our case, it will be
# <some module>.utilities. <some module> is the module we want to query the version for.
# <some module>._utilities. <some module> is the module we want to query the version for.
root_package, *rest = __name__.split('.')
# pkg_resources uses setuptools to inspect the set of installed packages. We use it here to ask

View file

@ -133,10 +133,12 @@ func (p shortcodeParser) Open(parent ast.Node, reader text.Reader, pc parser.Con
}
func (p shortcodeParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
line, _ := reader.PeekLine()
line, seg := reader.PeekLine()
pos := pc.BlockOffset()
if pos < 0 {
return parser.Continue | parser.HasChildren
} else if pos > seg.Len() {
return parser.Continue | parser.HasChildren
}
nameStart, nameEnd, shortcodeEnd, isClose, ok := p.parseShortcode(line, pos)

View file

@ -0,0 +1,79 @@
package schema
import (
"sync"
"github.com/blang/semver"
jsoniter "github.com/json-iterator/go"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
)
type Loader interface {
LoadPackage(pkg string, version *semver.Version) (*Package, error)
}
type pluginLoader struct {
m sync.RWMutex
host plugin.Host
entries map[string]*Package
}
func NewPluginLoader(host plugin.Host) Loader {
return &pluginLoader{
host: host,
entries: map[string]*Package{},
}
}
func (l *pluginLoader) getPackage(key string) (*Package, bool) {
l.m.RLock()
defer l.m.RUnlock()
p, ok := l.entries[key]
return p, ok
}
func (l *pluginLoader) LoadPackage(pkg string, version *semver.Version) (*Package, error) {
key := pkg + "@"
if version != nil {
key += version.String()
}
if p, ok := l.getPackage(key); ok {
return p, nil
}
provider, err := l.host.Provider(tokens.Package(pkg), version)
if err != nil {
return nil, err
}
schemaFormatVersion := 0
schemaBytes, err := provider.GetSchema(schemaFormatVersion)
if err != nil {
return nil, err
}
var spec PackageSpec
if err := jsoniter.Unmarshal(schemaBytes, &spec); err != nil {
return nil, err
}
p, err := ImportSpec(spec, nil)
if err != nil {
return nil, err
}
l.m.Lock()
defer l.m.Unlock()
if p, ok := l.entries[pkg]; ok {
return p, nil
}
l.entries[key] = p
return p, nil
}

View file

@ -303,6 +303,8 @@ type Package struct {
Repository string
// LogoURL is the URL for the package's logo, if any.
LogoURL string
// PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any.
PluginDownloadURL string
// Types is the list of non-resource types defined by the package.
Types []Type
@ -316,6 +318,9 @@ type Package struct {
Functions []*Function
// Language specifies additional language-specific data about the package.
Language map[string]interface{}
resourceTable map[string]*Resource
functionTable map[string]*Function
}
// Language provides hooks for importing language-specific metadata in a package.
@ -539,6 +544,16 @@ func (pkg *Package) TokenToModule(tok string) string {
}
}
func (pkg *Package) GetResource(token string) (*Resource, bool) {
r, ok := pkg.resourceTable[token]
return r, ok
}
func (pkg *Package) GetFunction(token string) (*Function, bool) {
f, ok := pkg.functionTable[token]
return f, ok
}
// TypeSpec is the serializable form of a reference to a type.
type TypeSpec struct {
// Type is the primitive or composite type, if any. May be "bool", "integer", "number", "string", "array", or
@ -681,6 +696,8 @@ type PackageSpec struct {
Repository string `json:"repository,omitempty"`
// LogoURL is the URL for the package's logo, if any.
LogoURL string `json:"logoUrl,omitempty"`
// PluginDownloadURL is the URL to use to acquire the provider plugin binary, if any.
PluginDownloadURL string `json:"pluginDownloadURL,omitempty"`
// Meta contains information for the importer about this package.
Meta *MetadataSpec `json:"meta,omitempty"`
@ -736,12 +753,12 @@ func ImportSpec(spec PackageSpec, languages map[string]Language) (*Package, erro
return nil, errors.Wrap(err, "binding provider")
}
resources, err := bindResources(spec.Resources, types)
resources, resourceTable, err := bindResources(spec.Resources, types)
if err != nil {
return nil, errors.Wrap(err, "binding resources")
}
functions, err := bindFunctions(spec.Functions, types)
functions, functionTable, err := bindFunctions(spec.Functions, types)
if err != nil {
return nil, errors.Wrap(err, "binding functions")
}
@ -774,21 +791,24 @@ func ImportSpec(spec PackageSpec, languages map[string]Language) (*Package, erro
}
pkg := &Package{
moduleFormat: moduleFormatRegexp,
Name: spec.Name,
Version: version,
Description: spec.Description,
Keywords: spec.Keywords,
Homepage: spec.Homepage,
License: spec.License,
Attribution: spec.Attribution,
Repository: spec.Repository,
Config: config,
Types: typeList,
Provider: provider,
Resources: resources,
Functions: functions,
Language: language,
moduleFormat: moduleFormatRegexp,
Name: spec.Name,
Version: version,
Description: spec.Description,
Keywords: spec.Keywords,
Homepage: spec.Homepage,
License: spec.License,
Attribution: spec.Attribution,
Repository: spec.Repository,
PluginDownloadURL: spec.PluginDownloadURL,
Config: config,
Types: typeList,
Provider: provider,
Resources: resources,
Functions: functions,
Language: language,
resourceTable: resourceTable,
functionTable: functionTable,
}
if err := pkg.ImportLanguages(languages); err != nil {
return nil, err
@ -1186,13 +1206,15 @@ func bindProvider(pkgName string, spec ResourceSpec, types *types) (*Resource, e
return res, nil
}
func bindResources(specs map[string]ResourceSpec, types *types) ([]*Resource, error) {
func bindResources(specs map[string]ResourceSpec, types *types) ([]*Resource, map[string]*Resource, error) {
resourceTable := map[string]*Resource{}
var resources []*Resource
for token, spec := range specs {
res, err := bindResource(token, spec, types)
if err != nil {
return nil, errors.Wrapf(err, "error binding resource %v", token)
return nil, nil, errors.Wrapf(err, "error binding resource %v", token)
}
resourceTable[token] = res
resources = append(resources, res)
}
@ -1200,7 +1222,7 @@ func bindResources(specs map[string]ResourceSpec, types *types) ([]*Resource, er
return resources[i].Token < resources[j].Token
})
return resources, nil
return resources, resourceTable, nil
}
func bindFunction(token string, spec FunctionSpec, types *types) (*Function, error) {
@ -1237,13 +1259,15 @@ func bindFunction(token string, spec FunctionSpec, types *types) (*Function, err
}, nil
}
func bindFunctions(specs map[string]FunctionSpec, types *types) ([]*Function, error) {
func bindFunctions(specs map[string]FunctionSpec, types *types) ([]*Function, map[string]*Function, error) {
functionTable := map[string]*Function{}
var functions []*Function
for token, spec := range specs {
f, err := bindFunction(token, spec, types)
if err != nil {
return nil, errors.Wrapf(err, "error binding function %v", token)
return nil, nil, errors.Wrapf(err, "error binding function %v", token)
}
functionTable[token] = f
functions = append(functions, f)
}
@ -1251,5 +1275,5 @@ func bindFunctions(specs map[string]FunctionSpec, types *types) ([]*Function, er
return functions[i].Token < functions[j].Token
})
return functions, nil
return functions, functionTable, nil
}

View file

@ -15,6 +15,9 @@
package codegen
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
@ -79,3 +82,27 @@ func SortedKeys(m interface{}) []string {
return keys
}
// CleanDir removes all existing files from a directory except those in the exclusions list.
// Note: The exclusions currently don't function recursively, so you cannot exclude a single file
// in a subdirectory, only entire subdirectories. This function will need improvements to be able to
// target that use-case.
func CleanDir(dirPath string, exclusions StringSet) error {
subPaths, err := ioutil.ReadDir(dirPath)
if err != nil {
return err
}
if len(subPaths) > 0 {
for _, path := range subPaths {
if !exclusions.Has(path.Name()) {
err = os.RemoveAll(filepath.Join(dirPath, path.Name()))
if err != nil {
return err
}
}
}
}
return nil
}

View file

@ -60,7 +60,7 @@ func printStepHeader(b io.StringWriter, step StepEventMetadata) {
// show a locked symbol, since we are either newly protecting this resource, or retaining protection.
extra = " 🔒"
}
writeString(b, fmt.Sprintf("%s: (%s)%s\n", string(step.Res.URN.Type()), step.Op, extra))
writeString(b, fmt.Sprintf("%s: (%s)%s\n", string(step.Type), step.Op, extra))
}
func GetIndentationString(indent int) string {
@ -117,7 +117,7 @@ func GetResourcePropertiesSummary(step StepEventMetadata, indent int) string {
var b bytes.Buffer
op := step.Op
urn := step.Res.URN
urn := step.URN
old := step.Old
// Print the indentation.
@ -232,25 +232,32 @@ func PrintObject(
}
}
func massageStackPreviewAdd(p resource.PropertyValue) {
func massageStackPreviewAdd(p resource.PropertyValue) resource.PropertyValue {
switch {
case p.IsArray():
for _, v := range p.ArrayValue() {
massageStackPreviewAdd(v)
arr := make([]resource.PropertyValue, len(p.ArrayValue()))
for i, v := range p.ArrayValue() {
arr[i] = massageStackPreviewAdd(v)
}
return resource.NewArrayProperty(arr)
case p.IsObject():
delete(p.ObjectValue(), "@isPulumiResource")
for _, v := range p.ObjectValue() {
massageStackPreviewAdd(v)
obj := resource.PropertyMap{}
for k, v := range p.ObjectValue() {
if k != "@isPulumiResource" {
obj[k] = massageStackPreviewAdd(v)
}
}
return resource.NewObjectProperty(obj)
default:
return p
}
}
func massageStackPreviewDiff(diff resource.ValueDiff, inResource bool) {
switch {
case diff.Array != nil:
for _, p := range diff.Array.Adds {
massageStackPreviewAdd(p)
for i, p := range diff.Array.Adds {
diff.Array.Adds[i] = massageStackPreviewAdd(p)
}
for _, d := range diff.Array.Updates {
massageStackPreviewDiff(d, inResource)
@ -278,8 +285,8 @@ func massageStackPreviewOutputDiff(diff *resource.ObjectDiff, inResource bool) {
}
}
for _, p := range diff.Adds {
massageStackPreviewAdd(p)
for i, p := range diff.Adds {
diff.Adds[i] = massageStackPreviewAdd(p)
}
for k, d := range diff.Updates {
if isResource && d.New.IsComputed() && !shouldPrintPropertyValue(d.Old, false) {
@ -296,7 +303,7 @@ func GetResourceOutputsPropertiesString(
step StepEventMetadata, indent int, planning, debug, refresh, showSames bool) string {
// During the actual update we always show all the outputs for the stack, even if they are unchanged.
if !showSames && !planning && step.Res.URN.Type() == resource.RootStackType {
if !showSames && !planning && step.URN.Type() == resource.RootStackType {
showSames = true
}
@ -321,7 +328,7 @@ func GetResourceOutputsPropertiesString(
step.Op == deploy.OpReadReplacement ||
step.Op == deploy.OpImport ||
step.Op == deploy.OpImportReplacement ||
step.Res.URN.Type() == resource.RootStackType
step.URN.Type() == resource.RootStackType
if !printOutputDuringPlanning {
return ""
}
@ -353,7 +360,7 @@ func GetResourceOutputsPropertiesString(
// If this is the root stack type, we want to strip out any nested resource outputs that are not known if
// they have no corresponding output in the old state.
if planning && step.Res.URN.Type() == resource.RootStackType {
if planning && step.URN.Type() == resource.RootStackType {
massageStackPreviewOutputDiff(outputDiff, false)
}
}

View file

@ -28,6 +28,7 @@ import (
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/deepcopy"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/logging"
)
@ -35,7 +36,38 @@ import (
// type for the `Payload` field will differ depending on the value of the `Type` field
type Event struct {
Type EventType
Payload interface{}
payload interface{}
}
func NewEvent(typ EventType, payload interface{}) Event {
ok := false
switch typ {
case CancelEvent:
ok = payload == nil
case StdoutColorEvent:
_, ok = payload.(StdoutEventPayload)
case DiagEvent:
_, ok = payload.(DiagEventPayload)
case PreludeEvent:
_, ok = payload.(PreludeEventPayload)
case SummaryEvent:
_, ok = payload.(SummaryEventPayload)
case ResourcePreEvent:
_, ok = payload.(ResourcePreEventPayload)
case ResourceOutputsEvent:
_, ok = payload.(ResourceOutputsEventPayload)
case ResourceOperationFailed:
_, ok = payload.(ResourceOperationFailedPayload)
case PolicyViolationEvent:
_, ok = payload.(PolicyViolationEventPayload)
default:
contract.Failf("unknown event type %v", typ)
}
contract.Assertf(ok, "invalid payload of type %T for event type %v", payload, typ)
return Event{
Type: typ,
payload: payload,
}
}
// EventType is the kind of event being emitted.
@ -53,6 +85,10 @@ const (
PolicyViolationEvent EventType = "policy-violation"
)
func (e Event) Payload() interface{} {
return deepcopy.Copy(e.payload)
}
func cancelEvent() Event {
return Event{Type: CancelEvent}
}
@ -119,6 +155,8 @@ type ResourcePreEventPayload struct {
// StepEventMetadata contains the metadata associated with a step the engine is performing.
type StepEventMetadata struct {
Op deploy.StepOp // the operation performed by this step.
URN resource.URN // the resource URN (for before and after).
Type tokens.Type // the type affected by this step.
Old *StepEventStateMetadata // the state of the resource before performing this step.
New *StepEventStateMetadata // the state of the resource after performing this step.
Res *StepEventStateMetadata // the latest state for the resource that is known (worst case, old).
@ -272,6 +310,8 @@ func makeStepEventMetadata(op deploy.StepOp, step deploy.Step, debug bool) StepE
return StepEventMetadata{
Op: op,
URN: step.URN(),
Type: step.Type(),
Keys: keys,
Diffs: diffs,
DetailedDiff: detailedDiff,
@ -425,27 +465,21 @@ func (e *eventEmitter) resourceOperationFailedEvent(
contract.Requiref(e != nil, "e", "!= nil")
e.ch <- Event{
Type: ResourceOperationFailed,
Payload: ResourceOperationFailedPayload{
Metadata: makeStepEventMetadata(step.Op(), step, debug),
Status: status,
Steps: steps,
},
}
e.ch <- NewEvent(ResourceOperationFailed, ResourceOperationFailedPayload{
Metadata: makeStepEventMetadata(step.Op(), step, debug),
Status: status,
Steps: steps,
})
}
func (e *eventEmitter) resourceOutputsEvent(op deploy.StepOp, step deploy.Step, planning bool, debug bool) {
contract.Requiref(e != nil, "e", "!= nil")
e.ch <- Event{
Type: ResourceOutputsEvent,
Payload: ResourceOutputsEventPayload{
Metadata: makeStepEventMetadata(op, step, debug),
Planning: planning,
Debug: debug,
},
}
e.ch <- NewEvent(ResourceOutputsEvent, ResourceOutputsEventPayload{
Metadata: makeStepEventMetadata(op, step, debug),
Planning: planning,
Debug: debug,
})
}
func (e *eventEmitter) resourcePreEvent(
@ -453,14 +487,11 @@ func (e *eventEmitter) resourcePreEvent(
contract.Requiref(e != nil, "e", "!= nil")
e.ch <- Event{
Type: ResourcePreEvent,
Payload: ResourcePreEventPayload{
Metadata: makeStepEventMetadata(step.Op(), step, debug),
Planning: planning,
Debug: debug,
},
}
e.ch <- NewEvent(ResourcePreEvent, ResourcePreEventPayload{
Metadata: makeStepEventMetadata(step.Op(), step, debug),
Planning: planning,
Debug: debug,
})
}
func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) {
@ -474,44 +505,35 @@ func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) {
configStringMap[keyString] = valueString
}
e.ch <- Event{
Type: PreludeEvent,
Payload: PreludeEventPayload{
IsPreview: isPreview,
Config: configStringMap,
},
}
e.ch <- NewEvent(PreludeEvent, PreludeEventPayload{
IsPreview: isPreview,
Config: configStringMap,
})
}
func (e *eventEmitter) previewSummaryEvent(resourceChanges ResourceChanges, policyPacks map[string]string) {
contract.Requiref(e != nil, "e", "!= nil")
e.ch <- Event{
Type: SummaryEvent,
Payload: SummaryEventPayload{
IsPreview: true,
MaybeCorrupt: false,
Duration: 0,
ResourceChanges: resourceChanges,
PolicyPacks: policyPacks,
},
}
e.ch <- NewEvent(SummaryEvent, SummaryEventPayload{
IsPreview: true,
MaybeCorrupt: false,
Duration: 0,
ResourceChanges: resourceChanges,
PolicyPacks: policyPacks,
})
}
func (e *eventEmitter) updateSummaryEvent(maybeCorrupt bool,
duration time.Duration, resourceChanges ResourceChanges, policyPacks map[string]string) {
contract.Requiref(e != nil, "e", "!= nil")
e.ch <- Event{
Type: SummaryEvent,
Payload: SummaryEventPayload{
IsPreview: false,
MaybeCorrupt: maybeCorrupt,
Duration: duration,
ResourceChanges: resourceChanges,
PolicyPacks: policyPacks,
},
}
e.ch <- NewEvent(SummaryEvent, SummaryEventPayload{
IsPreview: false,
MaybeCorrupt: maybeCorrupt,
Duration: duration,
ResourceChanges: resourceChanges,
PolicyPacks: policyPacks,
})
}
func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDiagnostic) {
@ -542,37 +564,31 @@ func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDi
buffer.WriteString(colors.Reset)
buffer.WriteRune('\n')
e.ch <- Event{
Type: PolicyViolationEvent,
Payload: PolicyViolationEventPayload{
ResourceURN: urn,
Message: logging.FilterString(buffer.String()),
Color: colors.Raw,
PolicyName: d.PolicyName,
PolicyPackName: d.PolicyPackName,
PolicyPackVersion: d.PolicyPackVersion,
EnforcementLevel: d.EnforcementLevel,
Prefix: logging.FilterString(prefix.String()),
},
}
e.ch <- NewEvent(PolicyViolationEvent, PolicyViolationEventPayload{
ResourceURN: urn,
Message: logging.FilterString(buffer.String()),
Color: colors.Raw,
PolicyName: d.PolicyName,
PolicyPackName: d.PolicyPackName,
PolicyPackVersion: d.PolicyPackVersion,
EnforcementLevel: d.EnforcementLevel,
Prefix: logging.FilterString(prefix.String()),
})
}
func diagEvent(e *eventEmitter, d *diag.Diag, prefix, msg string, sev diag.Severity,
ephemeral bool) {
contract.Requiref(e != nil, "e", "!= nil")
e.ch <- Event{
Type: DiagEvent,
Payload: DiagEventPayload{
URN: d.URN,
Prefix: logging.FilterString(prefix),
Message: logging.FilterString(msg),
Color: colors.Raw,
Severity: sev,
StreamID: d.StreamID,
Ephemeral: ephemeral,
},
}
e.ch <- NewEvent(DiagEvent, DiagEventPayload{
URN: d.URN,
Prefix: logging.FilterString(prefix),
Message: logging.FilterString(msg),
Color: colors.Raw,
Severity: sev,
StreamID: d.StreamID,
Ephemeral: ephemeral,
})
}
func (e *eventEmitter) diagDebugEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {

View file

@ -119,7 +119,7 @@ func (j *Journal) Snap(base *deploy.Snapshot) *deploy.Snapshot {
resources, dones := []*resource.State{}, make(map[*resource.State]bool)
ops, doneOps := []resource.Operation{}, make(map[*resource.State]bool)
for _, e := range j.Entries {
logging.V(7).Infof("%v %v (%v)", e.Step.Op(), e.Step.Res().URN, e.Kind)
logging.V(7).Infof("%v %v (%v)", e.Step.Op(), e.Step.URN(), e.Kind)
// Begin journal entries add pending operations to the snapshot. As we see success or failure
// entries, we'll record them in doneOps.
@ -229,7 +229,7 @@ func AssertSameSteps(t *testing.T, expected []StepSummary, actual []deploy.Step)
act := actual[0]
actual = actual[1:]
if !assert.Equal(t, exp.Op, act.Op()) || !assert.Equal(t, exp.URN, act.Res().URN) {
if !assert.Equal(t, exp.Op, act.Op()) || !assert.Equal(t, exp.URN, act.URN()) {
return false
}
}
@ -672,7 +672,7 @@ func TestSingleResourceDefaultProviderUpgrade(t *testing.T) {
// Should see only sames: the default provider should be injected into the old state before the update
// runs.
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
expect := deploy.OpSame
if isRefresh {
@ -707,7 +707,7 @@ func TestSingleResourceDefaultProviderUpgrade(t *testing.T) {
// runs.
deleted := make(map[resource.URN]bool)
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
deleted[urn] = true
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
@ -783,7 +783,7 @@ func TestSingleResourceDefaultProviderReplace(t *testing.T) {
continue
}
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
replacedProvider = true
case resURN:
@ -875,7 +875,7 @@ func TestSingleResourceExplicitProviderReplace(t *testing.T) {
continue
}
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
replacedProvider = true
case resURN:
@ -967,7 +967,7 @@ func TestSingleResourceExplicitProviderDeleteBeforeReplace(t *testing.T) {
continue
}
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
if entry.Step.Op() == deploy.OpDeleteReplaced {
assert.False(t, createdProvider)
@ -1050,7 +1050,7 @@ func TestSingleResourceDiffUnavailable(t *testing.T) {
found := false
for _, e := range events {
if e.Type == DiagEvent {
p := e.Payload.(DiagEventPayload)
p := e.Payload().(DiagEventPayload)
if p.URN == resURN && p.Severity == diag.Warning && p.Message == "diff unavailable" {
found = true
break
@ -1114,7 +1114,7 @@ func TestDestroyWithPendingDelete(t *testing.T) {
deletedID0, deletedID1 := false, false
for _, entry := range j.Entries {
// Ignore non-terminal steps and steps that affect the injected default provider.
if entry.Kind != JournalEntrySuccess || entry.Step.Res().URN != resURN ||
if entry.Kind != JournalEntrySuccess || entry.Step.URN() != resURN ||
(entry.Step.Op() != deploy.OpDelete && entry.Step.Op() != deploy.OpDeleteReplaced) {
continue
}
@ -1188,7 +1188,7 @@ func TestUpdateWithPendingDelete(t *testing.T) {
deletedID0, deletedID1 := false, false
for _, entry := range j.Entries {
// Ignore non-terminal steps and steps that affect the injected default provider.
if entry.Kind != JournalEntrySuccess || entry.Step.Res().URN != resURN ||
if entry.Kind != JournalEntrySuccess || entry.Step.URN() != resURN ||
(entry.Step.Op() != deploy.OpDelete && entry.Step.Op() != deploy.OpDeleteReplaced) {
continue
}
@ -1457,7 +1457,7 @@ func TestCheckFailureRecord(t *testing.T) {
sawFailure := false
for _, evt := range evts {
if evt.Type == DiagEvent {
e := evt.Payload.(DiagEventPayload)
e := evt.Payload().(DiagEventPayload)
msg := colors.Never.Colorize(e.Message)
sawFailure = msg == "oh no, check had an error\n" && e.Severity == diag.Error
}
@ -1507,7 +1507,7 @@ func TestCheckFailureInvalidPropertyRecord(t *testing.T) {
sawFailure := false
for _, evt := range evts {
if evt.Type == DiagEvent {
e := evt.Payload.(DiagEventPayload)
e := evt.Payload().(DiagEventPayload)
msg := colors.Never.Colorize(e.Message)
sawFailure = strings.Contains(msg, "field is not valid") && e.Severity == diag.Error
if sawFailure {
@ -1674,8 +1674,8 @@ func validateRefreshDeleteCombination(t *testing.T, names []string, targets []st
for _, entry := range j.Entries {
if len(refreshTargets) > 0 {
// should only see changes to urns we explicitly asked to change
assert.Containsf(t, refreshTargets, entry.Step.Res().URN,
"Refreshed a resource that wasn't a target: %v", entry.Step.Res().URN)
assert.Containsf(t, refreshTargets, entry.Step.URN(),
"Refreshed a resource that wasn't a target: %v", entry.Step.URN())
}
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
@ -1843,8 +1843,8 @@ func validateRefreshBasicsCombination(t *testing.T, names []string, targets []st
for _, entry := range j.Entries {
if len(refreshTargets) > 0 {
// should only see changes to urns we explicitly asked to change
assert.Containsf(t, refreshTargets, entry.Step.Res().URN,
"Refreshed a resource that wasn't a target: %v", entry.Step.Res().URN)
assert.Containsf(t, refreshTargets, entry.Step.URN(),
"Refreshed a resource that wasn't a target: %v", entry.Step.URN())
}
assert.Equal(t, deploy.OpRefresh, entry.Step.Op())
@ -2090,7 +2090,7 @@ func TestLanguageHostDiagnostics(t *testing.T) {
sawExitCode := false
for _, evt := range evts {
if evt.Type == DiagEvent {
e := evt.Payload.(DiagEventPayload)
e := evt.Payload().(DiagEventPayload)
msg := colors.Never.Colorize(e.Message)
sawExitCode = strings.Contains(msg, errorText) && e.Severity == diag.Error
if sawExitCode {
@ -2375,7 +2375,7 @@ func TestUpdatePartialFailure(t *testing.T) {
assertIsErrorOrBailResult(t, res)
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case resURN:
assert.Equal(t, deploy.OpUpdate, entry.Step.Op())
switch entry.Kind {
@ -2482,7 +2482,7 @@ func TestStackReference(t *testing.T) {
assert.Nil(t, res)
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case resURN:
switch entry.Step.Op() {
case deploy.OpCreateReplacement, deploy.OpDeleteReplaced, deploy.OpReplace:
@ -2811,7 +2811,7 @@ func TestDeleteBeforeReplace(t *testing.T) {
replaced := make(map[resource.URN]bool)
for _, entry := range j.Entries {
if entry.Step.Op() == deploy.OpReplace {
replaced[entry.Step.Res().URN] = true
replaced[entry.Step.URN()] = true
}
}
@ -3159,7 +3159,7 @@ func TestSingleResourceIgnoreChanges(t *testing.T) {
events []Event, res result.Result) result.Result {
for _, event := range events {
if event.Type == ResourcePreEvent {
payload := event.Payload.(ResourcePreEventPayload)
payload := event.Payload().(ResourcePreEventPayload)
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
}
}
@ -3255,7 +3255,7 @@ func TestDefaultProviderDiff(t *testing.T) {
continue
}
switch entry.Step.Res().URN.Name().String() {
switch entry.Step.URN().Name().String() {
case resName, resBName:
assert.Equal(t, expectedStep, entry.Step.Op())
}
@ -3376,7 +3376,7 @@ func TestDefaultProviderDiffReplacement(t *testing.T) {
continue
}
switch entry.Step.Res().URN.Name().String() {
switch entry.Step.URN().Name().String() {
case resName:
assert.Subset(t, expectedSteps, []deploy.StepOp{entry.Step.Op()})
case resBName:
@ -3494,13 +3494,13 @@ func TestAliases(t *testing.T) {
events []Event, res result.Result) result.Result {
for _, event := range events {
if event.Type == ResourcePreEvent {
payload := event.Payload.(ResourcePreEventPayload)
payload := event.Payload().(ResourcePreEventPayload)
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
}
}
for _, entry := range j.Entries {
if entry.Step.Res().URN.Type() == "pulumi:providers:pkgA" {
if entry.Step.Type() == "pulumi:providers:pkgA" {
continue
}
switch entry.Kind {
@ -3803,8 +3803,8 @@ func TestPersistentDiff(t *testing.T) {
found := false
for _, e := range events {
if e.Type == ResourcePreEvent {
p := e.Payload.(ResourcePreEventPayload).Metadata
if p.Res.URN == resURN {
p := e.Payload().(ResourcePreEventPayload).Metadata
if p.URN == resURN {
assert.Equal(t, deploy.OpUpdate, p.Op)
found = true
}
@ -3824,8 +3824,8 @@ func TestPersistentDiff(t *testing.T) {
found := false
for _, e := range events {
if e.Type == ResourcePreEvent {
p := e.Payload.(ResourcePreEventPayload).Metadata
if p.Res.URN == resURN {
p := e.Payload().(ResourcePreEventPayload).Metadata
if p.URN == resURN {
assert.Equal(t, deploy.OpSame, p.Op)
found = true
}
@ -3884,8 +3884,8 @@ func TestDetailedDiffReplace(t *testing.T) {
found := false
for _, e := range events {
if e.Type == ResourcePreEvent {
p := e.Payload.(ResourcePreEventPayload).Metadata
if p.Res.URN == resURN && p.Op == deploy.OpReplace {
p := e.Payload().(ResourcePreEventPayload).Metadata
if p.URN == resURN && p.Op == deploy.OpReplace {
found = true
}
}
@ -3973,7 +3973,7 @@ func TestImport(t *testing.T) {
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
case resURN:
@ -3991,7 +3991,7 @@ func TestImport(t *testing.T) {
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
default:
@ -4007,7 +4007,7 @@ func TestImport(t *testing.T) {
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
case resURN:
@ -4029,7 +4029,7 @@ func TestImport(t *testing.T) {
_, res = TestOp(Destroy).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
default:
@ -4045,7 +4045,7 @@ func TestImport(t *testing.T) {
snap, res = TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
default:
@ -4066,7 +4066,7 @@ func TestImport(t *testing.T) {
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
default:
@ -4083,7 +4083,7 @@ func TestImport(t *testing.T) {
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
case resURN:
@ -4106,7 +4106,7 @@ func TestImport(t *testing.T) {
snap, res = TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
case resURN:
@ -4125,7 +4125,7 @@ func TestImport(t *testing.T) {
_, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
case resURN:
@ -4211,7 +4211,7 @@ func TestImportWithDifferingImportIdentifierFormat(t *testing.T) {
snap, res := TestOp(Update).Run(project, p.GetTarget(nil), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN:
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
case resURN:
@ -4229,7 +4229,7 @@ func TestImportWithDifferingImportIdentifierFormat(t *testing.T) {
snap, res = TestOp(Update).Run(project, p.GetTarget(snap), p.Options, false, p.BackendClient,
func(_ workspace.Project, _ deploy.Target, j *Journal, _ []Event, res result.Result) result.Result {
for _, entry := range j.Entries {
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case provURN, resURN:
assert.Equal(t, deploy.OpSame, entry.Step.Op())
default:
@ -4349,7 +4349,7 @@ func TestProviderDiffMissingOldOutputs(t *testing.T) {
continue
}
switch urn := entry.Step.Res().URN; urn {
switch urn := entry.Step.URN(); urn {
case providerURN:
replacedProvider = true
case resURN:
@ -4600,7 +4600,7 @@ func destroySpecificTargets(
deleted := make(map[resource.URN]bool)
for _, entry := range j.Entries {
assert.Equal(t, deploy.OpDelete, entry.Step.Op())
deleted[entry.Step.Res().URN] = true
deleted[entry.Step.URN()] = true
}
for _, target := range p.Options.DestroyTargets {
@ -4693,9 +4693,9 @@ func updateSpecificTargets(t *testing.T, targets []string) {
sames := make(map[resource.URN]bool)
for _, entry := range j.Entries {
if entry.Step.Op() == deploy.OpUpdate {
updated[entry.Step.Res().URN] = true
updated[entry.Step.URN()] = true
} else if entry.Step.Op() == deploy.OpSame {
sames[entry.Step.Res().URN] = true
sames[entry.Step.URN()] = true
} else {
assert.FailNowf(t, "", "Got a step that wasn't a same/update: %v", entry.Step.Op())
}
@ -4803,9 +4803,9 @@ func TestCreateDuringTargetedUpdate_CreateMentionedAsTarget(t *testing.T) {
assert.True(t, len(j.Entries) > 0)
for _, entry := range j.Entries {
if entry.Step.Res().URN == resA {
if entry.Step.URN() == resA {
assert.Equal(t, deploy.OpSame, entry.Step.Op())
} else if entry.Step.Res().URN == resB {
} else if entry.Step.URN() == resB {
assert.Equal(t, deploy.OpCreate, entry.Step.Op())
}
}
@ -5072,7 +5072,7 @@ func TestDependencyChangeDBR(t *testing.T) {
resBDeleted, resBSame := false, false
for _, entry := range j.Entries {
if entry.Step.Res().URN == urnB {
if entry.Step.URN() == urnB {
switch entry.Step.Op() {
case deploy.OpDelete, deploy.OpDeleteReplaced:
resBDeleted = true
@ -5148,9 +5148,9 @@ func TestReplaceSpecificTargets(t *testing.T) {
sames := make(map[resource.URN]bool)
for _, entry := range j.Entries {
if entry.Step.Op() == deploy.OpReplace {
replaced[entry.Step.Res().URN] = true
replaced[entry.Step.URN()] = true
} else if entry.Step.Op() == deploy.OpSame {
sames[entry.Step.Res().URN] = true
sames[entry.Step.URN()] = true
}
}
@ -5566,7 +5566,7 @@ func TestIgnoreChangesGolangLifecycle(t *testing.T) {
events []Event, res result.Result) result.Result {
for _, event := range events {
if event.Type == ResourcePreEvent {
payload := event.Payload.(ResourcePreEventPayload)
payload := event.Payload().(ResourcePreEventPayload)
assert.Equal(t, []deploy.StepOp{deploy.OpCreate}, []deploy.StepOp{payload.Metadata.Op})
}
}

View file

@ -266,7 +266,7 @@ func newPlanActions(opts planOptions) *planActions {
func (acts *planActions) OnResourceStepPre(step deploy.Step) (interface{}, error) {
acts.MapLock.Lock()
acts.Seen[step.Res().URN] = step
acts.Seen[step.URN()] = step
acts.MapLock.Unlock()
// Skip reporting if necessary.
@ -292,7 +292,7 @@ func (acts *planActions) OnResourceStepPost(ctx interface{},
// global message.
reportedURN := resource.URN("")
if reportStep {
reportedURN = step.Res().URN
reportedURN = step.URN()
}
acts.Opts.Diag.Errorf(diag.GetPreviewFailedError(reportedURN), err)
@ -354,10 +354,10 @@ func (acts *planActions) OnPolicyViolation(urn resource.URN, d plugin.AnalyzeDia
}
func assertSeen(seen map[resource.URN]deploy.Step, step deploy.Step) {
_, has := seen[step.Res().URN]
contract.Assertf(has, "URN '%v' had not been marked as seen", step.Res().URN)
_, has := seen[step.URN()]
contract.Assertf(has, "URN '%v' had not been marked as seen", step.URN())
}
func isDefaultProviderStep(step deploy.Step) bool {
return providers.IsDefaultProvider(step.Res().URN)
return providers.IsDefaultProvider(step.URN())
}

View file

@ -504,7 +504,7 @@ func newUpdateActions(context *Context, u UpdateInfo, opts planOptions) *updateA
func (acts *updateActions) OnResourceStepPre(step deploy.Step) (interface{}, error) {
// Ensure we've marked this step as observed.
acts.MapLock.Lock()
acts.Seen[step.Res().URN] = step
acts.Seen[step.URN()] = step
acts.MapLock.Unlock()
// Skip reporting if necessary.
@ -540,7 +540,7 @@ func (acts *updateActions) OnResourceStepPost(
errorURN := resource.URN("")
if reportStep {
errorURN = step.Res().URN
errorURN = step.URN()
}
// Issue a true, bonafide error.
@ -589,7 +589,7 @@ func (acts *updateActions) OnResourceStepPost(
if status == resource.StatusPartialFailure && step.Op() == deploy.OpUpdate {
logging.V(7).Infof(
"OnResourceStepPost(%s): Step is partially-failed update, saving old inputs instead of new inputs",
step.Res().URN)
step.URN())
new := step.New()
old := step.Old()
contract.Assert(new != nil)

View file

@ -134,6 +134,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -323,12 +325,16 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=

View file

@ -282,7 +282,12 @@ func NewPlan(ctx *plugin.Context, target *Target, prev *Snapshot, source Source,
}, nil
}
func (p *Plan) diag() diag.Sink { return p.ctx.Diag }
func (p *Plan) Ctx() *plugin.Context { return p.ctx }
func (p *Plan) Target() *Target { return p.target }
func (p *Plan) Diag() diag.Sink { return p.ctx.Diag }
func (p *Plan) Prev() *Snapshot { return p.prev }
func (p *Plan) Olds() map[resource.URN]*resource.State { return p.olds }
func (p *Plan) Source() Source { return p.source }
func (p *Plan) GetProvider(ref providers.Reference) (plugin.Provider, bool) {
return p.providers.GetProvider(ref)
@ -298,7 +303,7 @@ func (p *Plan) generateURN(parent resource.URN, ty tokens.Type, name tokens.QNam
parentType = parent.QualifiedType()
}
return resource.NewURN(p.target.Name, p.source.Project(), parentType, ty, name)
return resource.NewURN(p.Target().Name, p.source.Project(), parentType, ty, name)
}
// defaultProviderURN generates the URN for the global provider given a package.

View file

@ -82,9 +82,9 @@ func (pe *planExecutor) checkTargets(targets []resource.URN, op StepOp) result.R
logging.V(7).Infof("Resource to %v (%v) could not be found in the stack.", op, target)
if strings.Contains(string(target), "$") {
pe.plan.diag().Errorf(diag.GetTargetCouldNotBeFoundError(), target)
pe.plan.Diag().Errorf(diag.GetTargetCouldNotBeFoundError(), target)
} else {
pe.plan.diag().Errorf(diag.GetTargetCouldNotBeFoundDidYouForgetError(), target)
pe.plan.Diag().Errorf(diag.GetTargetCouldNotBeFoundDidYouForgetError(), target)
}
}
}
@ -108,7 +108,7 @@ func (pe *planExecutor) reportExecResult(message string, preview bool) {
// reportError reports a single error to the executor's diag stream with the indicated URN for context.
func (pe *planExecutor) reportError(urn resource.URN, err error) {
pe.plan.diag().Errorf(diag.RawMessage(urn, err.Error()))
pe.plan.Diag().Errorf(diag.RawMessage(urn, err.Error()))
}
// Execute executes a plan to completion, using the given cancellation context and running a preview
@ -343,7 +343,7 @@ func (pe *planExecutor) performDeletes(
if targetsOpt != nil {
resourceToStep := make(map[*resource.State]Step)
for _, step := range deleteSteps {
resourceToStep[pe.plan.olds[step.Res().URN]] = step
resourceToStep[pe.plan.olds[step.URN()]] = step
}
pe.rebuildBaseState(resourceToStep, false /*refresh*/)
@ -400,7 +400,7 @@ func (pe *planExecutor) retirePendingDeletes(callerCtx context.Context, opts Opt
// Submit the deletes for execution and wait for them all to retire.
for _, antichain := range antichains {
for _, step := range antichain {
pe.plan.ctx.StatusDiag.Infof(diag.RawMessage(step.Res().URN, "completing deletion from previous update"))
pe.plan.Ctx().StatusDiag.Infof(diag.RawMessage(step.URN(), "completing deletion from previous update"))
}
tok := stepExec.ExecuteParallel(antichain)

View file

@ -39,6 +39,8 @@ type Source interface {
// Project returns the package name of the Pulumi project we are obtaining resources from.
Project() tokens.PackageName
// Info returns a serializable payload that can be used to stamp snapshots for future reconciliation.
Info() interface{}
// Iterate begins iterating the source. Error is non-nil upon failure; otherwise, a valid iterator is returned.
Iterate(ctx context.Context, opts Options, providers ProviderSource) (SourceIterator, result.Result)

View file

@ -35,6 +35,7 @@ type errorSource struct {
func (src *errorSource) Close() error { return nil }
func (src *errorSource) Project() tokens.PackageName { return src.project }
func (src *errorSource) Info() interface{} { return nil }
func (src *errorSource) Iterate(
ctx context.Context, opts Options, providers ProviderSource) (SourceIterator, result.Result) {

View file

@ -41,11 +41,11 @@ import (
// EvalRunInfo provides information required to execute and deploy resources within a package.
type EvalRunInfo struct {
Proj *workspace.Project // the package metadata.
Pwd string // the package's working directory.
Program string // the path to the program.
Args []string // any arguments to pass to the package.
Target *Target // the target being deployed into.
Proj *workspace.Project `json:"proj" yaml:"proj"` // the package metadata.
Pwd string `json:"pwd" yaml:"pwd"` // the package's working directory.
Program string `json:"program" yaml:"program"` // the path to the program.
Args []string `json:"args,omitempty" yaml:"args,omitempty"` // any arguments to pass to the package.
Target *Target `json:"target,omitempty" yaml:"target,omitempty"` // the target being deployed into.
}
// NewEvalSource returns a planning source that fetches resources by evaluating a package with a set of args and
@ -78,6 +78,13 @@ func (src *evalSource) Project() tokens.PackageName {
return src.runinfo.Proj.Name
}
// Stack is the name of the stack being targeted by this evaluation source.
func (src *evalSource) Stack() tokens.QName {
return src.runinfo.Target.Name
}
func (src *evalSource) Info() interface{} { return src.runinfo }
// Iterate will spawn an evaluator coroutine and prepare to interact with it on subsequent calls to Next.
func (src *evalSource) Iterate(
ctx context.Context, opts Options, providers ProviderSource) (SourceIterator, result.Result) {
@ -575,7 +582,51 @@ func (rm *resmon) Invoke(ctx context.Context, req *pulumirpc.InvokeRequest) (*pu
func (rm *resmon) StreamInvoke(
req *pulumirpc.InvokeRequest, stream pulumirpc.ResourceMonitor_StreamInvokeServer) error {
return fmt.Errorf("the resource monitor does not implement streaming invokes")
tok := tokens.ModuleMember(req.GetTok())
label := fmt.Sprintf("ResourceMonitor.StreamInvoke(%s)", tok)
providerReq, err := parseProviderRequest(tok.Package(), req.GetVersion())
if err != nil {
return err
}
prov, err := getProviderFromSource(rm.providers, rm.defaultProviders, providerReq, req.GetProvider())
if err != nil {
return err
}
args, err := plugin.UnmarshalProperties(
req.GetArgs(), plugin.MarshalOptions{Label: label, KeepUnknowns: true})
if err != nil {
return errors.Wrapf(err, "failed to unmarshal %v args", tok)
}
// Synchronously do the StreamInvoke and then return the arguments. This will block until the
// streaming operation completes!
logging.V(5).Infof("ResourceMonitor.StreamInvoke received: tok=%v #args=%v", tok, len(args))
failures, err := prov.StreamInvoke(tok, args, func(event resource.PropertyMap) error {
mret, err := plugin.MarshalProperties(event, plugin.MarshalOptions{Label: label, KeepUnknowns: true})
if err != nil {
return errors.Wrapf(err, "failed to marshal return")
}
return stream.Send(&pulumirpc.InvokeResponse{Return: mret})
})
if err != nil {
return errors.Wrapf(err, "streaming invocation of %v returned an error", tok)
}
var chkfails []*pulumirpc.CheckFailure
for _, failure := range failures {
chkfails = append(chkfails, &pulumirpc.CheckFailure{
Property: string(failure.Property),
Reason: failure.Reason,
})
}
if len(chkfails) > 0 {
return stream.Send(&pulumirpc.InvokeResponse{Failures: chkfails})
}
return nil
}
// ReadResource reads the current state associated with a resource from its provider plugin.

View file

@ -35,6 +35,7 @@ type fixedSource struct {
func (src *fixedSource) Close() error { return nil }
func (src *fixedSource) Project() tokens.PackageName { return src.ctx }
func (src *fixedSource) Info() interface{} { return nil }
func (src *fixedSource) Iterate(
ctx context.Context, opts Options, providers ProviderSource) (SourceIterator, result.Result) {

View file

@ -32,6 +32,7 @@ type nullSource struct {
func (src *nullSource) Close() error { return nil }
func (src *nullSource) Project() tokens.PackageName { return "" }
func (src *nullSource) Info() interface{} { return nil }
func (src *nullSource) Iterate(
ctx context.Context, opts Options, providers ProviderSource) (SourceIterator, result.Result) {

View file

@ -25,6 +25,7 @@ import (
"github.com/pulumi/pulumi/sdk/v2/go/common/diag/colors"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource"
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
"github.com/pulumi/pulumi/sdk/v2/go/common/util/logging"
)
@ -44,6 +45,8 @@ type Step interface {
Apply(preview bool) (resource.Status, StepCompleteFunc, error) // applies or previews this step.
Op() StepOp // the operation performed by this step.
URN() resource.URN // the resource URN (for before and after).
Type() tokens.Type // the type affected by this step.
Provider() string // the provider reference for this step.
Old() *resource.State // the state of the resource before performing this step.
New() *resource.State // the state of the resource after performing this step.
@ -109,7 +112,9 @@ func NewSkippedCreateStep(plan *Plan, reg RegisterResourceEvent, new *resource.S
func (s *SameStep) Op() StepOp { return OpSame }
func (s *SameStep) Plan() *Plan { return s.plan }
func (s *SameStep) Type() tokens.Type { return s.new.Type }
func (s *SameStep) Provider() string { return s.new.Provider }
func (s *SameStep) URN() resource.URN { return s.new.URN }
func (s *SameStep) Old() *resource.State { return s.old }
func (s *SameStep) New() *resource.State { return s.new }
func (s *SameStep) Res() *resource.State { return s.new }
@ -191,7 +196,9 @@ func (s *CreateStep) Op() StepOp {
return OpCreate
}
func (s *CreateStep) Plan() *Plan { return s.plan }
func (s *CreateStep) Type() tokens.Type { return s.new.Type }
func (s *CreateStep) Provider() string { return s.new.Provider }
func (s *CreateStep) URN() resource.URN { return s.new.URN }
func (s *CreateStep) Old() *resource.State { return s.old }
func (s *CreateStep) New() *resource.State { return s.new }
func (s *CreateStep) Res() *resource.State { return s.new }
@ -211,7 +218,7 @@ func (s *CreateStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err
return resource.StatusOK, nil, err
}
id, outs, rst, err := prov.Create(s.new.URN, s.new.Inputs, s.new.CustomTimeouts.Create)
id, outs, rst, err := prov.Create(s.URN(), s.new.Inputs, s.new.CustomTimeouts.Create)
if err != nil {
if rst != resource.StatusPartialFailure {
return rst, nil, err
@ -309,7 +316,9 @@ func (s *DeleteStep) Op() StepOp {
return OpDelete
}
func (s *DeleteStep) Plan() *Plan { return s.plan }
func (s *DeleteStep) Type() tokens.Type { return s.old.Type }
func (s *DeleteStep) Provider() string { return s.old.Provider }
func (s *DeleteStep) URN() resource.URN { return s.old.URN }
func (s *DeleteStep) Old() *resource.State { return s.old }
func (s *DeleteStep) New() *resource.State { return nil }
func (s *DeleteStep) Res() *resource.State { return s.old }
@ -331,7 +340,7 @@ func (s *DeleteStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err
return resource.StatusOK, nil, err
}
if rst, err := prov.Delete(s.old.URN, s.old.ID, s.old.Outputs, s.old.CustomTimeouts.Delete); err != nil {
if rst, err := prov.Delete(s.URN(), s.old.ID, s.old.Outputs, s.old.CustomTimeouts.Delete); err != nil {
return rst, nil, err
}
}
@ -358,7 +367,9 @@ func (s *RemovePendingReplaceStep) Op() StepOp {
return OpRemovePendingReplace
}
func (s *RemovePendingReplaceStep) Plan() *Plan { return s.plan }
func (s *RemovePendingReplaceStep) Type() tokens.Type { return s.old.Type }
func (s *RemovePendingReplaceStep) Provider() string { return s.old.Provider }
func (s *RemovePendingReplaceStep) URN() resource.URN { return s.old.URN }
func (s *RemovePendingReplaceStep) Old() *resource.State { return s.old }
func (s *RemovePendingReplaceStep) New() *resource.State { return nil }
func (s *RemovePendingReplaceStep) Res() *resource.State { return s.old }
@ -411,7 +422,9 @@ func NewUpdateStep(plan *Plan, reg RegisterResourceEvent, old *resource.State,
func (s *UpdateStep) Op() StepOp { return OpUpdate }
func (s *UpdateStep) Plan() *Plan { return s.plan }
func (s *UpdateStep) Type() tokens.Type { return s.new.Type }
func (s *UpdateStep) Provider() string { return s.new.Provider }
func (s *UpdateStep) URN() resource.URN { return s.new.URN }
func (s *UpdateStep) Old() *resource.State { return s.old }
func (s *UpdateStep) New() *resource.State { return s.new }
func (s *UpdateStep) Res() *resource.State { return s.new }
@ -434,7 +447,7 @@ func (s *UpdateStep) Apply(preview bool) (resource.Status, StepCompleteFunc, err
}
// Update to the combination of the old "all" state, but overwritten with new inputs.
outs, rst, upderr := prov.Update(s.old.URN, s.old.ID, s.old.Outputs, s.new.Inputs,
outs, rst, upderr := prov.Update(s.URN(), s.old.ID, s.old.Outputs, s.new.Inputs,
s.new.CustomTimeouts.Update, s.ignoreChanges)
if upderr != nil {
if rst != resource.StatusPartialFailure {
@ -502,7 +515,9 @@ func NewReplaceStep(plan *Plan, old *resource.State, new *resource.State,
func (s *ReplaceStep) Op() StepOp { return OpReplace }
func (s *ReplaceStep) Plan() *Plan { return s.plan }
func (s *ReplaceStep) Type() tokens.Type { return s.new.Type }
func (s *ReplaceStep) Provider() string { return s.new.Provider }
func (s *ReplaceStep) URN() resource.URN { return s.new.URN }
func (s *ReplaceStep) Old() *resource.State { return s.old }
func (s *ReplaceStep) New() *resource.State { return s.new }
func (s *ReplaceStep) Res() *resource.State { return s.new }
@ -581,7 +596,9 @@ func (s *ReadStep) Op() StepOp {
}
func (s *ReadStep) Plan() *Plan { return s.plan }
func (s *ReadStep) Type() tokens.Type { return s.new.Type }
func (s *ReadStep) Provider() string { return s.new.Provider }
func (s *ReadStep) URN() resource.URN { return s.new.URN }
func (s *ReadStep) Old() *resource.State { return s.old }
func (s *ReadStep) New() *resource.State { return s.new }
func (s *ReadStep) Res() *resource.State { return s.new }
@ -666,7 +683,9 @@ func NewRefreshStep(plan *Plan, old *resource.State, done chan<- bool) Step {
func (s *RefreshStep) Op() StepOp { return OpRefresh }
func (s *RefreshStep) Plan() *Plan { return s.plan }
func (s *RefreshStep) Type() tokens.Type { return s.old.Type }
func (s *RefreshStep) Provider() string { return s.old.Provider }
func (s *RefreshStep) URN() resource.URN { return s.old.URN }
func (s *RefreshStep) Old() *resource.State { return s.old }
func (s *RefreshStep) New() *resource.State { return s.new }
func (s *RefreshStep) Res() *resource.State { return s.old }
@ -719,7 +738,7 @@ func (s *RefreshStep) Apply(preview bool) (resource.Status, StepCompleteFunc, er
// `pulumi up` will surface them to the user.
err = nil
msg := fmt.Sprintf("Refreshed resource is in an unhealthy state:\n* %s", strings.Join(initErrors, "\n* "))
s.plan.diag().Warningf(diag.RawMessage(s.old.URN, msg))
s.Plan().Diag().Warningf(diag.RawMessage(s.URN(), msg))
}
}
outputs := refreshed.Outputs
@ -807,7 +826,9 @@ func (s *ImportStep) Op() StepOp {
}
func (s *ImportStep) Plan() *Plan { return s.plan }
func (s *ImportStep) Type() tokens.Type { return s.new.Type }
func (s *ImportStep) Provider() string { return s.new.Provider }
func (s *ImportStep) URN() resource.URN { return s.new.URN }
func (s *ImportStep) Old() *resource.State { return s.old }
func (s *ImportStep) New() *resource.State { return s.new }
func (s *ImportStep) Res() *resource.State { return s.new }
@ -1030,16 +1051,16 @@ func (op StepOp) Suffix() string {
// getProvider fetches the provider for the given step.
func getProvider(s Step) (plugin.Provider, error) {
if providers.IsProviderType(s.Res().URN.Type()) {
if providers.IsProviderType(s.Type()) {
return s.Plan().providers, nil
}
ref, err := providers.ParseReference(s.Provider())
if err != nil {
return nil, errors.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.Res().URN, err)
return nil, errors.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err)
}
provider, ok := s.Plan().GetProvider(ref)
if !ok {
return nil, errors.Errorf("unknown provider '%v' for resource %v", s.Provider(), s.Res().URN)
return nil, errors.Errorf("unknown provider '%v' for resource %v", s.Provider(), s.URN())
}
return provider, nil
}

View file

@ -173,8 +173,8 @@ func (se *stepExecutor) ExecuteRegisterResourceOutputs(e RegisterResourceOutputs
// or 2) promote RRE to be step-like so that it can be scheduled as if it were a step. Neither
// of these are particularly appealing right now.
outErr := errors.Wrap(eventerr, "resource complete event returned an error")
diagMsg := diag.RawMessage(reg.Res().URN, outErr.Error())
se.plan.diag().Errorf(diagMsg)
diagMsg := diag.RawMessage(reg.URN(), outErr.Error())
se.plan.Diag().Errorf(diagMsg)
se.cancelDueToError()
return
}
@ -213,13 +213,13 @@ func (se *stepExecutor) executeChain(workerID int, chain chain) {
for _, step := range chain {
select {
case <-se.ctx.Done():
se.log(workerID, "step %v on %v canceled", step.Op(), step.Res().URN)
se.log(workerID, "step %v on %v canceled", step.Op(), step.URN())
return
default:
}
if err := se.executeStep(workerID, step); err != nil {
se.log(workerID, "step %v on %v failed, signalling cancellation", step.Op(), step.Res().URN)
se.log(workerID, "step %v on %v failed, signalling cancellation", step.Op(), step.URN())
se.cancelDueToError()
if err != errStepApplyFailed {
// Step application errors are recorded by the OnResourceStepPost callback. This is confusing,
@ -227,8 +227,8 @@ func (se *stepExecutor) executeChain(workerID int, chain chain) {
//
// The errStepApplyFailed sentinel signals that the error that failed this chain was a step apply
// error and that we shouldn't log it. Everything else should be logged to the diag system as usual.
diagMsg := diag.RawMessage(step.Res().URN, err.Error())
se.plan.diag().Errorf(diagMsg)
diagMsg := diag.RawMessage(step.URN(), err.Error())
se.plan.Diag().Errorf(diagMsg)
}
return
}
@ -262,23 +262,23 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error {
var err error
payload, err = events.OnResourceStepPre(step)
if err != nil {
se.log(workerID, "step %v on %v failed pre-resource step: %v", step.Op(), step.Res().URN, err)
se.log(workerID, "step %v on %v failed pre-resource step: %v", step.Op(), step.URN(), err)
return errors.Wrap(err, "pre-step event returned an error")
}
}
se.log(workerID, "applying step %v on %v (preview %v)", step.Op(), step.Res().URN, se.preview)
se.log(workerID, "applying step %v on %v (preview %v)", step.Op(), step.URN(), se.preview)
status, stepComplete, err := step.Apply(se.preview)
if err == nil {
// If we have a state object, and this is a create or update, remember it, as we may need to update it later.
if step.Logical() && step.New() != nil {
if prior, has := se.pendingNews.Load(step.Res().URN); has {
if prior, has := se.pendingNews.Load(step.URN()); has {
return errors.Errorf(
"resource '%s' registered twice (%s and %s)", step.Res().URN, prior.(Step).Op(), step.Op())
"resource '%s' registered twice (%s and %s)", step.URN(), prior.(Step).Op(), step.Op())
}
se.pendingNews.Store(step.Res().URN, step)
se.pendingNews.Store(step.URN(), step)
}
}
@ -294,7 +294,7 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error {
if events != nil {
if postErr := events.OnResourceStepPost(payload, step, status, err); postErr != nil {
se.log(workerID, "step %v on %v failed post-resource step: %v", step.Op(), step.Res().URN, postErr)
se.log(workerID, "step %v on %v failed post-resource step: %v", step.Op(), step.URN(), postErr)
return errors.Wrap(postErr, "post-step event returned an error")
}
}
@ -302,12 +302,12 @@ func (se *stepExecutor) executeStep(workerID int, step Step) error {
// Calling stepComplete allows steps that depend on this step to continue. OnResourceStepPost saved the results
// of the step in the snapshot, so we are ready to go.
if stepComplete != nil {
se.log(workerID, "step %v on %v retired", step.Op(), step.Res().URN)
se.log(workerID, "step %v on %v retired", step.Op(), step.URN())
stepComplete()
}
if err != nil {
se.log(workerID, "step %v on %v failed with an error: %v", step.Op(), step.Res().URN, err)
se.log(workerID, "step %v on %v failed with an error: %v", step.Op(), step.URN(), err)
return errStepApplyFailed
}

View file

@ -111,7 +111,7 @@ func (sg *stepGenerator) GenerateReadSteps(event ReadResourceEvent) ([]Step, res
nil, /* customTimeouts */
"", /* importID */
)
old, hasOld := sg.plan.olds[urn]
old, hasOld := sg.plan.Olds()[urn]
// If the snapshot has an old resource for this URN and it's not external, we're going
// to have to delete the old resource and conceptually replace it with the resource we
@ -176,9 +176,9 @@ func (sg *stepGenerator) GenerateSteps(event RegisterResourceEvent) ([]Step, res
// Give a particular error in that case to let them know. Also mark that we're
// in an error state so that we eventually will error out of the entire
// application run.
d := diag.GetResourceWillBeCreatedButWasNotSpecifiedInTargetList(step.Res().URN)
d := diag.GetResourceWillBeCreatedButWasNotSpecifiedInTargetList(step.URN())
sg.plan.diag().Errorf(d, step.Res().URN, urn)
sg.plan.Diag().Errorf(d, step.URN(), urn)
sg.sawError = true
if !sg.plan.preview {
@ -209,7 +209,7 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res
if sg.urns[urn] {
invalid = true
// TODO[pulumi/pulumi-framework#19]: improve this error message!
sg.plan.diag().Errorf(diag.GetDuplicateResourceURNError(urn), urn)
sg.plan.Diag().Errorf(diag.GetDuplicateResourceURNError(urn), urn)
}
sg.urns[urn] = true
@ -221,14 +221,14 @@ func (sg *stepGenerator) generateSteps(event RegisterResourceEvent) ([]Step, res
var old *resource.State
var hasOld bool
for _, urnOrAlias := range append([]resource.URN{urn}, goal.Aliases...) {
old, hasOld = sg.plan.olds[urnOrAlias]
old, hasOld = sg.plan.Olds()[urnOrAlias]
if hasOld {
oldInputs = old.Inputs
oldOutputs = old.Outputs
if urnOrAlias != urn {
if previousAliasURN, alreadyAliased := sg.aliased[urnOrAlias]; alreadyAliased {
invalid = true
sg.plan.diag().Errorf(diag.GetDuplicateResourceAliasError(urn), urnOrAlias, urn, previousAliasURN)
sg.plan.Diag().Errorf(diag.GetDuplicateResourceAliasError(urn), urnOrAlias, urn, previousAliasURN)
}
sg.aliased[urnOrAlias] = urn
}
@ -725,7 +725,7 @@ func (sg *stepGenerator) GenerateDeletes(targetsOpt map[resource.URN]bool) ([]St
if allowedResourcesToDelete != nil {
filtered := []Step{}
for _, step := range dels {
if _, has := allowedResourcesToDelete[step.Res().URN]; has {
if _, has := allowedResourcesToDelete[step.URN()]; has {
filtered = append(filtered, step)
}
}
@ -735,7 +735,7 @@ func (sg *stepGenerator) GenerateDeletes(targetsOpt map[resource.URN]bool) ([]St
deletingUnspecifiedTarget := false
for _, step := range dels {
urn := step.Res().URN
urn := step.URN()
if targetsOpt != nil && !targetsOpt[urn] && !sg.opts.TargetDependents {
d := diag.GetResourceWillBeDestroyedButWasNotSpecifiedInTargetList(urn)
@ -744,7 +744,7 @@ func (sg *stepGenerator) GenerateDeletes(targetsOpt map[resource.URN]bool) ([]St
// re-running the operation.
//
// Mark that step generation entered an error state so that the entire app run fails.
sg.plan.diag().Errorf(d, urn)
sg.plan.Diag().Errorf(d, urn)
sg.sawError = true
deletingUnspecifiedTarget = true
@ -814,7 +814,7 @@ func (sg *stepGenerator) determineAllowedResourcesToDeleteFromTargets(
}
if _, has := resourcesToDelete[res.Parent]; has {
sg.plan.diag().Errorf(diag.GetCannotDeleteParentResourceWithoutAlsoDeletingChildError(res.Parent),
sg.plan.Diag().Errorf(diag.GetCannotDeleteParentResourceWithoutAlsoDeletingChildError(res.Parent),
res.Parent, res.URN)
return nil, result.Bail()
}
@ -1084,10 +1084,10 @@ func issueCheckErrors(plan *Plan, new *resource.State, urn resource.URN, failure
inputs := new.Inputs
for _, failure := range failures {
if failure.Property != "" {
plan.diag().Errorf(diag.GetResourcePropertyInvalidValueError(urn),
plan.Diag().Errorf(diag.GetResourcePropertyInvalidValueError(urn),
new.Type, urn.Name(), failure.Property, inputs[failure.Property], failure.Reason)
} else {
plan.diag().Errorf(
plan.Diag().Errorf(
diag.GetResourceInvalidError(urn), new.Type, urn.Name(), failure.Reason)
}
}
@ -1150,12 +1150,12 @@ func (sg *stepGenerator) loadResourceProvider(
contract.Assert(provider != "")
ref, refErr := providers.ParseReference(provider)
if refErr != nil {
sg.plan.diag().Errorf(diag.GetBadProviderError(urn), provider, urn, refErr)
sg.plan.Diag().Errorf(diag.GetBadProviderError(urn), provider, urn, refErr)
return nil, result.Bail()
}
p, ok := sg.plan.GetProvider(ref)
if !ok {
sg.plan.diag().Errorf(diag.GetUnknownProviderError(urn), provider, urn)
sg.plan.Diag().Errorf(diag.GetUnknownProviderError(urn), provider, urn)
return nil, result.Bail()
}
return p, nil
@ -1340,7 +1340,7 @@ func (sg *stepGenerator) AnalyzeResources() result.Result {
}
}
if urn == "" {
urn = resource.DefaultRootStackURN(sg.plan.target.Name, sg.plan.source.Project())
urn = resource.DefaultRootStackURN(sg.plan.Target().Name, sg.plan.source.Project())
}
sg.opts.Events.OnPolicyViolation(urn, d)
}

View file

@ -87,7 +87,7 @@ publish_containers() {
docker logout
# This publishes the SDK specific containers and uses a dispatch event to trigger a GitHub Action
pulumictl create containers ${CLI_VERSION}
pulumictl create containers "${CLI_VERSION//v}"
}
echo_header "Building Pulumi containers (${CLI_VERSION})"

View file

@ -24,6 +24,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -80,10 +81,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=

View file

@ -17,14 +17,24 @@
<NoWarn>NU5105</NoWarn>
</PropertyGroup>
<PropertyGroup>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pulumi\Pulumi.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
<ItemGroup>
<None Include="..\pulumi_logo_64x64.png">
<Pack>True</Pack>

View file

@ -1,7 +1,8 @@
// Copyright 2016-2019, Pulumi Corporation
// Copyright 2016-2020, Pulumi Corporation
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Pulumi
@ -92,6 +93,7 @@ namespace Pulumi
{
if (_inFlightTasks.Count == 0)
{
// No more tasks in flight: exit the loop.
break;
}
@ -99,24 +101,56 @@ namespace Pulumi
tasks.AddRange(_inFlightTasks.Keys);
}
// Now, wait for one of them to finish.
var task = await Task.WhenAny(tasks).ConfigureAwait(false);
List<string> descriptions;
lock (_inFlightTasks)
// Wait for one of the two events to happen:
// 1. All tasks in the list complete successfully, or
// 2. Any task throws an exception.
// There's no standard API with this semantics, so we create a custom completion source that is
// completed when remaining count is zero, or when an exception is thrown.
var remaining = tasks.Count;
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
tasks.ForEach(HandleCompletion);
async void HandleCompletion(Task task)
{
// Once finished, remove it from the set of tasks that are running.
descriptions = _inFlightTasks[task];
_inFlightTasks.Remove(task);
try
{
// Wait for the task completion.
await task.ConfigureAwait(false);
// Log the descriptions of completed tasks.
var descriptions = _inFlightTasks[task];
foreach (var description in descriptions)
{
Serilog.Log.Information($"Completed task: {description}");
}
// Check if all the tasks are completed and signal the completion source if so.
if (Interlocked.Decrement(ref remaining) == 0)
{
tcs.TrySetResult(0);
}
}
catch (OperationCanceledException)
{
tcs.TrySetCanceled();
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
finally
{
// Once finished, remove the task from the set of tasks that are running.
lock (_inFlightTasks)
{
_inFlightTasks.Remove(task);
}
}
}
foreach (var description in descriptions)
Serilog.Log.Information($"Completed task: {description}");
try
{
// Now actually await that completed task so that we will realize any exceptions
// is may have thrown.
await task.ConfigureAwait(false);
// Now actually await that combined task and realize any exceptions it may have thrown.
await tcs.Task.ConfigureAwait(false);
}
catch (Exception e)
{

View file

@ -21,26 +21,26 @@ namespace Pulumi
var type = res.GetResourceType();
var name = res.GetResourceName();
Log.Debug($"Gathering explicit dependencies: t={type}, name={name}, custom={custom}");
LogExcessive($"Gathering explicit dependencies: t={type}, name={name}, custom={custom}");
var explicitDirectDependencies = new HashSet<Resource>(
await GatherExplicitDependenciesAsync(options.DependsOn).ConfigureAwait(false));
Log.Debug($"Gathered explicit dependencies: t={type}, name={name}, custom={custom}");
LogExcessive($"Gathered explicit dependencies: t={type}, name={name}, custom={custom}");
// Serialize out all our props to their final values. In doing so, we'll also collect all
// the Resources pointed to by any Dependency objects we encounter, adding them to 'propertyDependencies'.
Log.Debug($"Serializing properties: t={type}, name={name}, custom={custom}");
LogExcessive($"Serializing properties: t={type}, name={name}, custom={custom}");
var dictionary = await args.ToDictionaryAsync().ConfigureAwait(false);
var (serializedProps, propertyToDirectDependencies) =
await SerializeResourcePropertiesAsync(label, dictionary).ConfigureAwait(false);
Log.Debug($"Serialized properties: t={type}, name={name}, custom={custom}");
LogExcessive($"Serialized properties: t={type}, name={name}, custom={custom}");
// Wait for the parent to complete.
// If no parent was provided, parent to the root resource.
Log.Debug($"Getting parent urn: t={type}, name={name}, custom={custom}");
LogExcessive($"Getting parent urn: t={type}, name={name}, custom={custom}");
var parentURN = options.Parent != null
? await options.Parent.Urn.GetValueAsync().ConfigureAwait(false)
: await GetRootResourceAsync(type).ConfigureAwait(false);
Log.Debug($"Got parent urn: t={type}, name={name}, custom={custom}");
LogExcessive($"Got parent urn: t={type}, name={name}, custom={custom}");
string? providerRef = null;
if (custom)
@ -89,6 +89,12 @@ namespace Pulumi
allDirectDependencyURNs,
propertyToDirectDependencyURNs,
aliases);
void LogExcessive(string message)
{
if (_excessiveDebugOutput)
Log.Debug(message);
}
}
private static Task<ImmutableArray<Resource>> GatherExplicitDependenciesAsync(InputList<Resource> resources)

View file

@ -12,7 +12,7 @@ namespace Pulumi
{
public partial class Deployment
{
internal static bool _excessiveDebugOutput = true;
internal static bool _excessiveDebugOutput = false;
/// <summary>
/// <see cref="SerializeResourcePropertiesAsync"/> walks the props object passed in,

View file

@ -19,7 +19,14 @@
<NoWarn>1701;1702;1591;NU5105</NoWarn>
</PropertyGroup>
<PropertyGroup>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="2.9.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -15,6 +15,8 @@ require (
github.com/golang/protobuf v1.3.5
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645
github.com/hashicorp/go-multierror v1.0.0
github.com/kr/pretty v0.2.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-runewidth v0.0.8 // indirect
github.com/mitchellh/go-ps v1.0.0

View file

@ -32,6 +32,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -104,10 +105,14 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=

View file

@ -208,3 +208,21 @@ func decryptAES256GCM(ciphertext []byte, key []byte, nonce []byte) (string, erro
return string(msg), err
}
// Crypter that just adds a prefix to the plaintext string when encrypting,
// and removes the prefix from the ciphertext when decrypting, for use in tests.
type prefixCrypter struct {
prefix string
}
func newPrefixCrypter(prefix string) Crypter {
return prefixCrypter{prefix: prefix}
}
func (c prefixCrypter) DecryptValue(ciphertext string) (string, error) {
return strings.TrimPrefix(ciphertext, c.prefix), nil
}
func (c prefixCrypter) EncryptValue(plaintext string) (string, error) {
return c.prefix + plaintext, nil
}

View file

@ -43,6 +43,20 @@ func (m Map) Decrypt(decrypter Decrypter) (map[Key]string, error) {
return r, nil
}
func (m Map) Copy(decrypter Decrypter, encrypter Encrypter) (Map, error) {
newConfig := make(Map)
for k, c := range m {
val, err := c.Copy(decrypter, encrypter)
if err != nil {
return nil, err
}
newConfig[k] = val
}
return newConfig, nil
}
// HasSecureValue returns true if the config map contains a secure (encrypted) value.
func (m Map) HasSecureValue() bool {
for _, v := range m {

View file

@ -1170,6 +1170,82 @@ func TestSetFail(t *testing.T) {
}
}
func TestCopyMap(t *testing.T) {
tests := []struct {
Config Map
Expected Map
}{
{
Config: Map{
MustMakeKey("my", "testKey"): NewValue("testValue"),
},
Expected: Map{
MustMakeKey("my", "testKey"): NewValue("testValue"),
},
},
{
Config: Map{
MustMakeKey("my", "testKey"): NewSecureValue("stackAsecurevalue"),
},
Expected: Map{
MustMakeKey("my", "testKey"): NewSecureValue("stackBsecurevalue"),
},
},
{
Config: Map{
MustMakeKey("my", "testKey"): NewObjectValue(`{"inner":"value"}`),
},
Expected: Map{
MustMakeKey("my", "testKey"): NewObjectValue(`{"inner":"value"}`),
},
},
{
Config: Map{
MustMakeKey("my", "testKey"): NewSecureObjectValue(`{"inner":{"secure":"stackAsecurevalue"}}`),
},
Expected: Map{
MustMakeKey("my", "testKey"): NewSecureObjectValue(`{"inner":{"secure":"stackBsecurevalue"}}`),
},
},
{
Config: Map{
//nolint:lll
MustMakeKey("my", "testKey"): NewSecureObjectValue(`[{"inner":{"secure":"stackAsecurevalue"}},{"secure":"stackAsecurevalue2"}]`),
},
Expected: Map{
//nolint:lll
MustMakeKey("my", "testKey"): NewSecureObjectValue(`[{"inner":{"secure":"stackBsecurevalue"}},{"secure":"stackBsecurevalue2"}]`),
},
},
{
Config: Map{
MustMakeKey("my", "test.Key"): NewValue("testValue"),
},
Expected: Map{
MustMakeKey("my", "test.Key"): NewValue("testValue"),
},
},
{
Config: Map{
MustMakeKey("my", "name"): NewObjectValue(`[["value"]]`),
},
Expected: Map{
MustMakeKey("my", "name"): NewObjectValue(`[["value"]]`),
},
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
newConfig, err := test.Config.Copy(newPrefixCrypter("stackA"), newPrefixCrypter("stackB"))
assert.NoError(t, err)
assert.Equal(t, test.Expected, newConfig)
})
}
}
func roundtripMapYAML(m Map) (Map, error) {
return roundtripMap(m, yaml.Marshal, yaml.Unmarshal)
}

View file

@ -72,6 +72,46 @@ func (c Value) Value(decrypter Decrypter) (string, error) {
return decrypter.DecryptValue(c.value)
}
func (c Value) Copy(decrypter Decrypter, encrypter Encrypter) (Value, error) {
var val Value
raw, err := c.Value(decrypter)
if err != nil {
return Value{}, err
}
if c.Secure() {
if c.Object() {
objVal, err := c.ToObject()
if err != nil {
return Value{}, err
}
encryptedObj, err := reencryptObject(objVal, decrypter, encrypter)
if err != nil {
return Value{}, err
}
json, err := json.Marshal(encryptedObj)
if err != nil {
return Value{}, err
}
val = NewSecureObjectValue(string(json))
} else {
enc, eerr := encrypter.EncryptValue(raw)
if eerr != nil {
return Value{}, eerr
}
val = NewSecureValue(enc)
}
} else {
if c.Object() {
val = NewObjectValue(raw)
} else {
val = NewValue(raw)
}
}
return val, nil
}
func (c Value) SecureValues(decrypter Decrypter) ([]string, error) {
d := NewTrackingDecrypter(decrypter)
if _, err := c.Value(d); err != nil {
@ -240,6 +280,53 @@ func isSecureValue(v interface{}) (bool, string) {
return false, ""
}
func reencryptObject(v interface{}, decrypter Decrypter, encrypter Encrypter) (interface{}, error) {
reencryptIt := func(val interface{}) (interface{}, error) {
if isSecure, secureVal := isSecureValue(val); isSecure {
newVal := NewSecureValue(secureVal)
raw, err := newVal.Value(decrypter)
if err != nil {
return nil, err
}
encVal, err := encrypter.EncryptValue(raw)
if err != nil {
return nil, err
}
m := make(map[string]string)
m["secure"] = encVal
return m, nil
}
return reencryptObject(val, decrypter, encrypter)
}
switch t := v.(type) {
case map[string]interface{}:
m := make(map[string]interface{})
for key, val := range t {
encrypted, err := reencryptIt(val)
if err != nil {
return nil, err
}
m[key] = encrypted
}
return m, nil
case []interface{}:
a := make([]interface{}, len(t))
for i, val := range t {
encrypted, err := reencryptIt(val)
if err != nil {
return nil, err
}
a[i] = encrypted
}
return a, nil
}
return v, nil
}
// decryptObject returns a new object with all secure values in the object converted to decrypted strings.
func decryptObject(v interface{}, decrypter Decrypter) (interface{}, error) {
decryptIt := func(val interface{}) (interface{}, error) {

View file

@ -245,6 +245,43 @@ func TestSecureValues(t *testing.T) {
}
}
func TestCopyValue(t *testing.T) {
tests := []struct {
Val Value
Expected Value
}{
{
Val: NewValue("value"),
Expected: NewValue("value"),
},
{
Val: NewObjectValue(`{"foo":"bar"}`),
Expected: NewObjectValue(`{"foo":"bar"}`),
},
{
Val: NewSecureObjectValue(`{"foo":{"secure":"stackAsecurevalue"}}`),
Expected: NewSecureObjectValue(`{"foo":{"secure":"stackBsecurevalue"}}`),
},
{
Val: NewSecureValue("stackAsecurevalue"),
Expected: NewSecureValue("stackBsecurevalue"),
},
{
Val: NewSecureObjectValue(`["a",{"secure":"stackAalpha"},{"test":{"secure":"stackAbeta"}}]`),
Expected: NewSecureObjectValue(`["a",{"secure":"stackBalpha"},{"test":{"secure":"stackBbeta"}}]`),
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%v", test), func(t *testing.T) {
newConfig, err := test.Val.Copy(newPrefixCrypter("stackA"), newPrefixCrypter("stackB"))
assert.NoError(t, err)
assert.Equal(t, test.Expected, newConfig)
})
}
}
func roundtripValueYAML(v Value) (Value, error) {
return roundtripValue(v, yaml.Marshal, yaml.Unmarshal)
}

View file

@ -128,7 +128,7 @@ func (p PropertyPath) Get(v PropertyValue) (PropertyValue, bool) {
return v, true
}
// Set attempts to set the location inside a PropertyValue indicated by the PropertyPath to the given value If any
// Set attempts to set the location inside a PropertyValue indicated by the PropertyPath to the given value. If any
// component of the path besides the last component does not exist, this function will return false.
func (p PropertyPath) Set(dest, v PropertyValue) bool {
if len(p) == 0 {
@ -160,6 +160,72 @@ func (p PropertyPath) Set(dest, v PropertyValue) bool {
return true
}
// Add sets the location inside a PropertyValue indicated by the PropertyPath to the given value. Any components
// referred to by the path that do not exist will be created. If there is a mismatch between the type of an existing
// component and a key that traverses that component, this function will return false. If the destination is a null
// property value, this function will create and return a new property value.
func (p PropertyPath) Add(dest, v PropertyValue) (PropertyValue, bool) {
if len(p) == 0 {
return PropertyValue{}, false
}
// set sets the destination referred to by the last element of the path to the given value.
rv := dest
set := func(v PropertyValue) {
dest, rv = v, v
}
for _, key := range p {
switch key := key.(type) {
case int:
// This key is an int, so we expect an array.
switch {
case dest.IsNull():
// If the destination array does not exist, create a new array with enough room to store the value at
// the requested index.
dest = NewArrayProperty(make([]PropertyValue, key+1))
set(dest)
case dest.IsArray():
// If the destination array does exist, ensure that it is large enough to accommodate the requested
// index.
arr := dest.ArrayValue()
if key >= len(arr) {
arr = append(make([]PropertyValue, key+1-len(arr)), arr...)
v.V = arr
}
default:
return PropertyValue{}, false
}
destV := dest.ArrayValue()
set = func(v PropertyValue) {
destV[key] = v
}
dest = destV[key]
case string:
// This key is a string, so we expect an object.
switch {
case dest.IsNull():
// If the destination does not exist, create a new object.
dest = NewObjectProperty(PropertyMap{})
set(dest)
case dest.IsObject():
// OK
default:
return PropertyValue{}, false
}
destV := dest.ObjectValue()
set = func(v PropertyValue) {
destV[PropertyKey(key)] = v
}
dest = destV[PropertyKey(key)]
default:
return PropertyValue{}, false
}
}
set(v)
return rv, true
}
// Delete attempts to delete the value located by the PropertyPath inside the given PropertyValue. If any component
// of the path does not exist, this function will return false.
func (p PropertyPath) Delete(dest PropertyValue) bool {

View file

@ -133,6 +133,14 @@ func TestPropertyPath(t *testing.T) {
u, ok := parsed.Get(value)
assert.True(t, ok)
assert.Equal(t, v, u)
vv := PropertyValue{}
vv, ok = parsed.Add(vv, v)
assert.True(t, ok)
u, ok = parsed.Get(vv)
assert.True(t, ok)
assert.Equal(t, v, u)
})
}

View file

@ -0,0 +1,102 @@
// Copyright 2016-2020, 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 deepcopy
import "reflect"
// Copy returns a deep copy of the provided value.
//
// If there are multiple references to the same value inside the provided value, the multiply-referenced value will be
// copied multiple times.
func Copy(i interface{}) interface{} {
if i == nil {
return nil
}
return copy(reflect.ValueOf(i)).Interface()
}
func copy(v reflect.Value) reflect.Value {
if !v.IsValid() {
return v
}
typ := v.Type()
switch typ.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128,
reflect.String,
reflect.Func:
// These all have value semantics. Return them as-is.
return v
case reflect.Chan:
// Channels have referential semantics, but deep-copying them has no meaning. Return them as-is.
return v
case reflect.Interface:
rv := reflect.New(typ).Elem()
if !v.IsNil() {
rv.Set(copy(v.Elem()))
}
return rv
case reflect.Ptr:
if v.IsNil() {
return reflect.New(typ).Elem()
}
elem := copy(v.Elem())
if elem.CanAddr() {
return elem.Addr()
}
rv := reflect.New(typ.Elem())
rv.Set(elem)
return rv
case reflect.Array:
rv := reflect.New(typ).Elem()
for i := 0; i < v.Len(); i++ {
rv.Index(i).Set(copy(v.Index(i)))
}
return rv
case reflect.Slice:
rv := reflect.New(typ).Elem()
if !v.IsNil() {
rv.Set(reflect.MakeSlice(typ, v.Len(), v.Cap()))
for i := 0; i < v.Len(); i++ {
rv.Index(i).Set(copy(v.Index(i)))
}
}
return rv
case reflect.Map:
rv := reflect.New(typ).Elem()
if !v.IsNil() {
rv.Set(reflect.MakeMap(typ))
iter := v.MapRange()
for iter.Next() {
rv.SetMapIndex(copy(iter.Key()), copy(iter.Value()))
}
}
return rv
case reflect.Struct:
rv := reflect.New(typ).Elem()
for i := 0; i < typ.NumField(); i++ {
if f := rv.Field(i); f.CanSet() {
f.Set(copy(v.Field(i)))
}
}
return rv
default:
panic("unexpected kind " + typ.Kind().String())
}
}

View file

@ -0,0 +1,80 @@
// Copyright 2016-2020, 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 deepcopy
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDeepCopy(t *testing.T) {
cases := []interface{}{
bool(false),
bool(true),
int(-42),
int8(-42),
int16(-42),
int32(-42),
int64(-42),
uint(42),
uint8(42),
uint16(42),
uint32(42),
uint64(42),
float32(3.14159),
float64(3.14159),
complex64(complex(3.14159, -42)),
complex(3.14159, -42),
"foo",
[2]byte{42, 24},
[]byte{0, 1, 2, 3},
[]string{"foo", "bar"},
map[string]int{
"a": 42,
"b": 24,
},
struct {
Foo int
Bar map[int]int
}{
Foo: 42,
Bar: map[int]int{
19: 77,
},
},
[]map[string]string{
{
"foo": "bar",
"baz": "qux",
},
{
"alpha": "beta",
},
},
map[string]interface{}{
"foo": map[string]interface{}{
"bar": "baz",
},
"bar": []int{42},
},
}
for i, c := range cases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
assert.EqualValues(t, c, Copy(c))
})
}
}

View file

@ -483,6 +483,7 @@ func (ctx *Context) RegisterResource(
Aliases: inputs.aliases,
AcceptSecrets: true,
AdditionalSecretOutputs: inputs.additionalSecretOutputs,
Version: inputs.version,
})
if err != nil {
logging.V(9).Infof("RegisterResource(%s, %s): error: %v", t, name, err)
@ -790,6 +791,7 @@ type resourceInputs struct {
ignoreChanges []string
aliases []string
additionalSecretOutputs []string
version string
}
// prepareResourceInputs prepares the inputs for a resource operation, shared between read and register.
@ -801,7 +803,7 @@ func (ctx *Context) prepareResourceInputs(props Input, t string,
// Get the parent and dependency URNs from the options, in addition to the protection bit. If there wasn't an
// explicit parent, and a root stack resource exists, we will automatically parent to that.
parent, optDeps, protect, provider, deleteBeforeReplace,
importID, ignoreChanges, additionalSecretOutputs, err := ctx.getOpts(t, providers, opts)
importID, ignoreChanges, additionalSecretOutputs, version, err := ctx.getOpts(t, providers, opts)
if err != nil {
return nil, fmt.Errorf("resolving options: %w", err)
}
@ -874,6 +876,7 @@ func (ctx *Context) prepareResourceInputs(props Input, t string,
ignoreChanges: ignoreChanges,
aliases: aliases,
additionalSecretOutputs: additionalSecretOutputs,
version: version,
}, nil
}
@ -890,13 +893,13 @@ func getTimeouts(custom *CustomTimeouts) *pulumirpc.RegisterResourceRequest_Cust
// getOpts returns a set of resource options from an array of them. This includes the parent URN, any dependency URNs,
// a boolean indicating whether the resource is to be protected, and the URN and ID of the resource's provider, if any.
func (ctx *Context) getOpts(t string, providers map[string]ProviderResource, opts *resourceOptions) (
URN, []URN, bool, string, bool, ID, []string, []string, error) {
URN, []URN, bool, string, bool, ID, []string, []string, string, error) {
var importID ID
if opts.Import != nil {
id, _, _, err := opts.Import.ToIDOutput().awaitID(context.TODO())
if err != nil {
return "", nil, false, "", false, "", nil, nil, err
return "", nil, false, "", false, "", nil, nil, "", err
}
importID = id
}
@ -905,7 +908,7 @@ func (ctx *Context) getOpts(t string, providers map[string]ProviderResource, opt
if opts.Parent != nil {
urn, _, _, err := opts.Parent.URN().awaitURN(context.TODO())
if err != nil {
return "", nil, false, "", false, "", nil, nil, err
return "", nil, false, "", false, "", nil, nil, "", err
}
parentURN = urn
}
@ -916,7 +919,7 @@ func (ctx *Context) getOpts(t string, providers map[string]ProviderResource, opt
for i, r := range opts.DependsOn {
urn, _, _, err := r.URN().awaitURN(context.TODO())
if err != nil {
return "", nil, false, "", false, "", nil, nil, err
return "", nil, false, "", false, "", nil, nil, "", err
}
depURNs[i] = urn
}
@ -932,13 +935,13 @@ func (ctx *Context) getOpts(t string, providers map[string]ProviderResource, opt
if provider != nil {
pr, err := ctx.resolveProviderReference(provider)
if err != nil {
return "", nil, false, "", false, "", nil, nil, err
return "", nil, false, "", false, "", nil, nil, "", err
}
providerRef = pr
}
return parentURN, depURNs, opts.Protect, providerRef, opts.DeleteBeforeReplace,
importID, opts.IgnoreChanges, opts.AdditionalSecretOutputs, nil
importID, opts.IgnoreChanges, opts.AdditionalSecretOutputs, opts.Version, nil
}
func (ctx *Context) resolveProviderReference(provider ProviderResource) (string, error) {

View file

@ -181,6 +181,10 @@ type resourceOptions struct {
// The transformations are applied in order, and are applied prior to transformation and to parents
// walking from the resource up to the stack.
Transformations []ResourceTransformation
// An optional version, corresponding to the version of the provider plugin that should be used when operating on
// this resource. This version overrides the version information inferred from the current package and should
// rarely be used.
Version string
}
type invokeOptions struct {
@ -315,6 +319,15 @@ func Timeouts(o *CustomTimeouts) ResourceOption {
})
}
// An optional version, corresponding to the version of the provider plugin that should be used when operating on
// this resource. This version overrides the version information inferred from the current package and should
// rarely be used.
func Version(o string) ResourceOption {
return resourceOption(func(ro *resourceOptions) {
ro.Version = o
})
}
// Ignore changes to any of the specified properties.
func IgnoreChanges(o []string) ResourceOption {
return resourceOption(func(ro *resourceOptions) {

View file

@ -104,7 +104,7 @@ export async function streamInvoke(
queue.push(live);
});
call.on("error", (err: any) => {
if (err.code === 1 && err.details === "Cancelled") {
if (err.code === 1) {
return;
}
throw err;

View file

@ -18,8 +18,6 @@ providers and libraries in the Pulumi ecosystem use to create and manage
resources.
"""
import importlib
# Make all module members inside of this package available as package members.
from .asset import (
Asset,
@ -86,6 +84,4 @@ from .stack_reference import (
StackReference,
)
# Make subpackages available.
for pkg in ['runtime', 'dynamic', 'policy']:
importlib.import_module(f'{__name__}.{pkg}')
from . import runtime, dynamic, policy

View file

@ -18,10 +18,7 @@ Assets are the Pulumi notion of data blobs that can be passed to resources.
from os import PathLike, fspath
from typing import Dict, Union
from .runtime import known_types
@known_types.asset
class Asset:
"""
Asset represents a single blob of text or data that is managed as a first
@ -29,64 +26,59 @@ class Asset:
"""
@known_types.file_asset
class FileAsset(Asset):
path: str
"""
A FileAsset is a kind of asset produced from a given path to a file on
the local filesysetm.
the local filesystem.
"""
path: str
def __init__(self, path: Union[str, PathLike]) -> None:
if not isinstance(path, (str, PathLike)):
raise TypeError("FileAsset path must be a string or os.PathLike")
self.path = fspath(path)
@known_types.string_asset
class StringAsset(Asset):
text: str
"""
A StringAsset is a kind of asset produced from an in-memory UTF-8 encoded string.
"""
text: str
def __init__(self, text: str) -> None:
if not isinstance(text, str):
raise TypeError("StringAsset data must be a string")
self.text = text
@known_types.remote_asset
class RemoteAsset(Asset):
uri: str
"""
A RemoteAsset is a kind of asset produced from a given URI string. The URI's scheme
dictates the protocol for fetching contents: "file://" specifies a local file, "http://"
and "https://" specify HTTP and HTTPS, respectively. Note that specific providers may recognize
alternative schemes; this is merely the base-most set that all providers support.
"""
uri: str
def __init__(self, uri: str) -> None:
if not isinstance(uri, str):
raise TypeError("RemoteAsset URI must be a string")
self.uri = uri
@known_types.archive
class Archive:
"""
Asset represents a collection of named assets.
Archive represents a collection of named assets.
"""
@known_types.asset_archive
class AssetArchive(Archive):
assets: Dict[str, Union[Asset, Archive]]
"""
An AssetArchive is an archive created from an in-memory collection of named assets or other archives.
"""
assets: Dict[str, Union[Asset, Archive]]
def __init__(self, assets: Dict[str, Union[Asset, Archive]]) -> None:
if not isinstance(assets, dict):
raise TypeError("AssetArchive assets must be a dictionary")
@ -98,29 +90,27 @@ class AssetArchive(Archive):
self.assets = assets
@known_types.file_archive
class FileArchive(Archive):
path: str
"""
A FileArchive is a file-based archive, or collection of file-based assets. This can be
a raw directory or a single archive file in one of the supported formats (.tar, .tar.gz, or .zip).
"""
path: str
def __init__(self, path: str) -> None:
if not isinstance(path, str):
raise TypeError("FileArchive path must be a string")
self.path = path
@known_types.remote_archive
class RemoteArchive(Archive):
uri: str
"""
A RemoteArchive is a file-based archive fetched from a remote location. The URI's scheme dictates
the protocol for fetching contents: "file://" specifies a local file, "http://" and "https://"
specify HTTP and HTTPS, respectively, and specific providers may recognize custom schemes.
"""
uri: str
def __init__(self, uri: str) -> None:
if not isinstance(uri, str):
raise TypeError("RemoteArchive URI must be a string")

View file

@ -30,7 +30,6 @@ from typing import (
)
from . import runtime
from .runtime import known_types
from .runtime import rpc
if TYPE_CHECKING:
@ -43,7 +42,6 @@ Input = Union[T, Awaitable[T], 'Output[T]']
Inputs = Mapping[str, Input[Any]]
@known_types.output
class Output(Generic[T]):
"""
Output helps encode the relationship between Resources in a Pulumi application. Specifically an
@ -223,7 +221,6 @@ class Output(Generic[T]):
"""
return self.apply(lambda v: UNKNOWN if isinstance(v, Unknown) else getattr(v, item), True)
def __getitem__(self, key: Any) -> 'Output[Any]':
"""
Syntax sugar for looking up attributes dynamically off of outputs.
@ -371,7 +368,6 @@ class Output(Generic[T]):
return Output.all(*transformed_items).apply("".join) # type: ignore
@known_types.unknown
class Unknown:
"""
Unknown represents a value that is unknown.
@ -380,10 +376,12 @@ class Unknown:
def __init__(self):
pass
UNKNOWN = Unknown()
"""
UNKNOWN is the singleton unknown value.
"""
def contains_unknowns(val: Any) -> bool:
return rpc.contains_unknowns(val)

View file

@ -23,15 +23,11 @@ from .runtime.settings import get_root_resource
from .metadata import get_project, get_stack
from .output import Output
if TYPE_CHECKING:
from .output import Input, Inputs
from .output import Input, Inputs, Output
from .runtime.stack import Stack
@known_types.custom_timeouts
class CustomTimeouts:
create: Optional[str]
"""
@ -81,6 +77,7 @@ def inherited_child_alias(
# * parentAliasName: "app"
# * aliasName: "app-function"
# * childAlias: "urn:pulumi:stackname::projectname::aws:s3/bucket:Bucket::app-function"
from . import Output # pylint: disable=import-outside-toplevel
alias_name = Output.from_input(child_name)
if child_name.startswith(parent_name):
alias_name = Output.from_input(parent_alias).apply(
@ -163,14 +160,14 @@ class Alias:
"""
# Ignoring type errors associated with the ellipsis constant being assigned to a string value.
# We use it as a internal sentinal value, and don't need to expose this in the user facing type system.
# We use it as a internal sentinel value, and don't need to expose this in the user facing type system.
# https://docs.python.org/3/library/constants.html#Ellipsis
def __init__(self,
name: Optional[str] = ..., # type: ignore
type_: Optional[str] = ..., # type: ignore
parent: Optional[Union['Resource', 'Input[str]']] = ..., # type: ignore
stack: Optional['Input[str]'] = ..., # type: ignore
project: Optional['Input[str]'] = ...) -> None: # type: ignore
name: Optional[str] = ..., # type: ignore
type_: Optional[str] = ..., # type: ignore
parent: Optional[Union['Resource', 'Input[str]']] = ..., # type: ignore
stack: Optional['Input[str]'] = ..., # type: ignore
project: Optional['Input[str]'] = ...) -> None: # type: ignore
self.name = name
self.type_ = type_
@ -187,8 +184,9 @@ def collapse_alias_to_urn(
"""
collapse_alias_to_urn turns an Alias into a URN given a set of default data
"""
from . import Output # pylint: disable=import-outside-toplevel
def collapse_alias_to_urn_worker(inner: Union[Alias, str]) -> 'Output[str]':
def collapse_alias_to_urn_worker(inner: Union[Alias, str]) -> Output[str]:
if isinstance(inner, str):
return Output.from_input(inner)
@ -209,6 +207,7 @@ def collapse_alias_to_urn(
inputAlias: Output[Union[Alias, str]] = Output.from_input(alias)
return inputAlias.apply(collapse_alias_to_urn_worker)
class ResourceTransformationArgs:
"""
ResourceTransformationArgs is the argument bag passed to a resource transformation.
@ -251,6 +250,7 @@ class ResourceTransformationArgs:
self.props = props
self.opts = opts
class ResourceTransformationResult:
"""
ResourceTransformationResult is the result that must be returned by a resource transformation
@ -274,6 +274,7 @@ class ResourceTransformationResult:
self.props = props
self.opts = opts
ResourceTransformation = Callable[[ResourceTransformationArgs], Optional[ResourceTransformationResult]]
"""
ResourceTransformation is the callback signature for the `transformations` resource option. A
@ -284,6 +285,7 @@ of the original call to the `Resource` constructor. If the transformation retur
this indicates that the resource will not be transformed.
"""
class ResourceOptions:
"""
ResourceOptions is a bag of optional settings that control a resource's behavior.
@ -458,7 +460,7 @@ class ResourceOptions:
values from each options object. Both original collections in each options object will
be unchanged.
2. Simple scaler values from `opts2` (i.e. strings, numbers, bools) will replace the values
2. Simple scalar values from `opts2` (i.e. strings, numbers, bools) will replace the values
from `opts1`.
3. For the purposes of merging `depends_on`, `provider` and `providers` are always treated
@ -551,6 +553,7 @@ def _merge_lists(dest, source):
return dest + source
# !!! IMPORTANT !!! If you add a new attribute to this type, make sure to verify that merge_options
# works properly for it.
class Resource:
@ -621,8 +624,8 @@ class Resource:
elif not isinstance(opts, ResourceOptions):
raise TypeError('Expected resource options to be a ResourceOptions instance')
# Before anything else - if there are transformations registered, give them a chance to run to modify the user provided
# properties and options assigned to this resource.
# Before anything else - if there are transformations registered, give them a chance to run to modify the user
# provided properties and options assigned to this resource.
parent = opts.parent
if parent is None:
parent = get_root_resource()
@ -667,7 +670,7 @@ class Resource:
for parent_alias in opts.parent._aliases:
child_alias = inherited_child_alias(
name, opts.parent._name, parent_alias, t)
opts.aliases.append(cast(Output[Union[str, Alias]], child_alias))
opts.aliases.append(cast('Output[Union[str, Alias]]', child_alias))
# Infer providers and provider maps from parent, if one was provided.
self._providers = opts.parent._providers
@ -776,7 +779,6 @@ class Resource:
return self._providers.get(pkg)
@known_types.custom_resource
class CustomResource(Resource):
"""
CustomResource is a resource whose create, read, update, and delete (CRUD) operations are
@ -903,7 +905,7 @@ def create_urn(
create_urn computes a URN from the combination of a resource name, resource type, optional
parent, optional project and optional stack.
"""
from . import Output # pylint: disable=import-outside-toplevel
parent_prefix: Optional[Output[str]] = None
if parent is not None:
parent_urn = None

View file

@ -13,11 +13,10 @@
# limitations under the License.
import asyncio
import sys
from typing import Any, Awaitable
from typing import Any, Awaitable, TYPE_CHECKING
import grpc
from .. import log
from ..output import Inputs
from ..invoke import InvokeOptions
from ..runtime.proto import provider_pb2
from . import rpc
@ -25,6 +24,9 @@ from .rpc_manager import RPC_MANAGER
from .settings import get_monitor
from .sync_await import _sync_await
if TYPE_CHECKING:
from .. import Inputs
# This setting overrides a hardcoded maximum protobuf size in the python protobuf bindings. This avoids deserialization
# exceptions on large gRPC payloads, but makes it possible to use enough memory to cause an OOM error instead [1].
# Note: We hit the default maximum protobuf size in practice when processing Kubernetes CRDs. If this setting ends up
@ -57,7 +59,7 @@ class InvokeResult:
__iter__ = __await__
def invoke(tok: str, props: Inputs, opts: InvokeOptions = None) -> InvokeResult:
def invoke(tok: str, props: 'Inputs', opts: InvokeOptions = None) -> InvokeResult:
"""
invoke dynamically invokes the function, tok, which is offered by a provider plugin. The inputs
can be a bag of computed values (Ts or Awaitable[T]s), and the result is a Awaitable[Any] that

View file

@ -12,8 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
The known_types module contains state for keeping track of types that
are known to be special in the Pulumi type system.
The known_types module lazily loads classes defined in the parent module to
allow for type checking.
Python strictly disallows circular references between imported packages.
Because the Pulumi top-level module depends on the `pulumi.runtime` submodule,
@ -21,285 +21,69 @@ it is not allowed for `pulumi.runtime` to reach back to the `pulumi` top-level
to reference types that are defined there.
In order to break this circular reference, and to be clear about what types
the runtime knows about and treats specially, this module exports a number of
"known type" decorators that can be applied to types in `pulumi` to indicate
that they are specially treated.
The implementation of this mechanism is that, for every known type, that type
is stashed away in a global variable. Whenever the runtime wants to do a type
test using that type (or instantiate an instance of this type), it uses the
functions defined in this module to do so.
the runtime knows about and treats specially, we defer loading of the types from
within the functions themselves.
"""
from typing import Any, Optional
# We override this global in test/test_next_serialize.py to stub the CustomResource type.
# TODO: Rework the test to remove the need for this global. https://github.com/pulumi/pulumi/issues/5000
_custom_resource_type: Optional[type] = None
"""The type of CustomResource. Filled-in as the Pulumi package is initializing."""
_custom_timeouts_type: Optional[type] = None
"""The type of CustomTimeouts. Filled-in as the Pulumi package is initializing."""
_asset_resource_type: Optional[type] = None
"""The type of Asset. Filled-in as the Pulumi package is initializing."""
_file_asset_resource_type: Optional[type] = None
"""The type of FileAsset. Filled-in as the Pulumi package is initializing."""
_string_asset_resource_type: Optional[type] = None
"""The type of StringAsset. Filled-in as the Pulumi package is initializing."""
_remote_asset_resource_type: Optional[type] = None
"""The type of RemoteAsset. Filled-in as the Pulumi package is initializing."""
_archive_resource_type: Optional[type] = None
"""The type of Archive. Filled-in as the Pulumi package is initializing."""
_asset_archive_resource_type: Optional[type] = None
"""The type of AssetArchive. Filled-in as the Pulumi package is initializing."""
_file_archive_resource_type: Optional[type] = None
"""The type of FileArchive. Filled-in as the Pulumi package is initializing."""
_remote_archive_resource_type: Optional[type] = None
"""The type of RemoteArchive. Filled-in as the Pulumi package is initializing."""
_stack_resource_type: Optional[type] = None
"""The type of Stack. Filled-in as the Pulumi package is initializing."""
_output_type: Optional[type] = None
"""The type of Output. Filled-in as the Pulumi package is initializing."""
_unknown_type: Optional[type] = None
"""The type of unknown. Filled-in as the Pulumi package is initializing."""
def asset(class_obj: type) -> type:
"""
Decorator to annotate the Asset class. Registers the decorated class
as the Asset known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _asset_resource_type
_asset_resource_type = class_obj
return class_obj
def file_asset(class_obj: type) -> type:
"""
Decorator to annotate the FileAsset class. Registers the decorated class
as the FileAsset known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _file_asset_resource_type
_file_asset_resource_type = class_obj
return class_obj
def string_asset(class_obj: type) -> type:
"""
Decorator to annotate the StringAsset class. Registers the decorated class
as the StringAsset known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _string_asset_resource_type
_string_asset_resource_type = class_obj
return class_obj
def remote_asset(class_obj: type) -> type:
"""
Decorator to annotate the RemoteAsset class. Registers the decorated class
as the RemoteAsset known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _remote_asset_resource_type
_remote_asset_resource_type = class_obj
return class_obj
def archive(class_obj: type) -> type:
"""
Decorator to annotate the Archive class. Registers the decorated class
as the Archive known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _archive_resource_type
_archive_resource_type = class_obj
return class_obj
def asset_archive(class_obj: type) -> type:
"""
Decorator to annotate the AssetArchive class. Registers the decorated class
as the AssetArchive known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _asset_archive_resource_type
_asset_archive_resource_type = class_obj
return class_obj
def file_archive(class_obj: type) -> type:
"""
Decorator to annotate the FileArchive class. Registers the decorated class
as the FileArchive known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _file_archive_resource_type
_file_archive_resource_type = class_obj
return class_obj
def remote_archive(class_obj: type) -> type:
"""
Decorator to annotate the RemoteArchive class. Registers the decorated class
as the RemoteArchive known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _remote_archive_resource_type
_remote_archive_resource_type = class_obj
return class_obj
def custom_resource(class_obj: type) -> type:
"""
Decorator to annotate the CustomResource class. Registers the decorated class
as the CustomResource known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _custom_resource_type
_custom_resource_type = class_obj
return class_obj
def custom_timeouts(class_obj: type) -> type:
"""
Decorator to annotate the CustomTimeouts class. Registers the decorated class
as the CustomTimeouts known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _custom_timeouts_type
_custom_timeouts_type = class_obj
return class_obj
def stack(class_obj: type) -> type:
"""
Decorator to annotate the Stack class. Registers the decorated class
as the Stack known type.
"""
assert isinstance(class_obj, type), "class_obj is not a Class"
global _stack_resource_type
_stack_resource_type = class_obj
return class_obj
def output(class_obj: type) -> type:
assert isinstance(class_obj, type), "class_obj is not a Class"
global _output_type
_output_type = class_obj
return class_obj
def unknown(class_obj: type) -> type:
assert isinstance(class_obj, type), "class_obj is not a Class"
global _unknown_type
_unknown_type = class_obj
return class_obj
def new_file_asset(*args: Any) -> Any:
"""
Instantiates a new FileAsset, passing the given arguments to the constructor.
"""
return _file_asset_resource_type(*args) # type: ignore
def new_string_asset(*args: Any) -> Any:
"""
Instantiates a new StringAsset, passing the given arguments to the constructor.
"""
return _string_asset_resource_type(*args) # type: ignore
def new_remote_asset(*args: Any) -> Any:
"""
Instantiates a new StringAsset, passing the given arguments to the constructor.
"""
return _remote_asset_resource_type(*args) # type: ignore
def new_asset_archive(*args: Any) -> Any:
"""
Instantiates a new AssetArchive, passing the given arguments to the constructor.
"""
return _asset_archive_resource_type(*args) # type: ignore
def new_file_archive(*args: Any) -> Any:
"""
Instantiates a new FileArchive, passing the given arguments to the constructor.
"""
return _file_archive_resource_type(*args) # type: ignore
def new_remote_archive(*args: Any) -> Any:
"""
Instantiates a new StringArchive, passing the given arguments to the constructor.
"""
return _remote_archive_resource_type(*args) # type: ignore
def new_output(*args: Any) -> Any:
"""
Instantiates a new Output, passing the given arguments to the constructor.
"""
return _output_type(*args) # type: ignore
def new_unknown(*args: Any) -> Any:
"""
Instantiates a new Unknown, passing the given arguments to the constructor.
"""
return _unknown_type(*args) # type: ignore
"""The type of CustomResource."""
def is_asset(obj: Any) -> bool:
"""
Returns true if the given type is an Asset, false otherwise.
"""
return _asset_resource_type is not None and isinstance(obj, _asset_resource_type)
from .. import Asset # pylint: disable=import-outside-toplevel
return isinstance(obj, Asset)
def is_archive(obj: Any) -> bool:
"""
Returns true if the given type is an Archive, false otherwise.
"""
return _archive_resource_type is not None and isinstance(obj, _archive_resource_type)
from .. import Archive # pylint: disable=import-outside-toplevel
return isinstance(obj, Archive)
def is_custom_resource(obj: Any) -> bool:
"""
Returns true if the given type is a CustomResource, false otherwise.
"""
return _custom_resource_type is not None and isinstance(obj, _custom_resource_type)
from .. import CustomResource # pylint: disable=import-outside-toplevel
return isinstance(obj, _custom_resource_type or CustomResource)
def is_custom_timeouts(obj: Any) -> bool:
"""
Returns true if the given type is a CustomTimeouts, false otherwise.
"""
return _custom_timeouts_type is not None and isinstance(obj, _custom_timeouts_type)
from .. import CustomTimeouts # pylint: disable=import-outside-toplevel
return isinstance(obj, CustomTimeouts)
def is_stack(obj: Any) -> bool:
"""
Returns true if the given type is an Output, false otherwise.
"""
return _stack_resource_type is not None and isinstance(obj, _stack_resource_type)
from .stack import Stack # pylint: disable=import-outside-toplevel
return isinstance(obj, Stack)
def is_output(obj: Any) -> bool:
"""
Returns true if the given type is an Output, false otherwise.
"""
return _output_type is not None and isinstance(obj, _output_type)
from .. import Output # pylint: disable=import-outside-toplevel
return isinstance(obj, Output)
def is_unknown(obj: Any) -> bool:
"""
Returns true if the given object is an Unknown, false otherwise.
"""
return _unknown_type is not None and isinstance(obj, _unknown_type)
from ..output import Unknown # pylint: disable=import-outside-toplevel
return isinstance(obj, Unknown)

View file

@ -27,7 +27,6 @@ from .settings import Settings, configure, get_stack, get_project, get_root_reso
from .sync_await import _sync_await
from ..runtime.proto import engine_pb2, engine_pb2_grpc, provider_pb2, resource_pb2, resource_pb2_grpc
from ..runtime.stack import Stack, run_pulumi_func
from ..output import Output
if TYPE_CHECKING:
from ..resource import Resource
@ -35,6 +34,7 @@ if TYPE_CHECKING:
def test(fn):
def wrapper(*args, **kwargs):
from .. import Output # pylint: disable=import-outside-toplevel
_sync_await(run_pulumi_func(lambda: _sync_await(Output.from_input(fn(*args, **kwargs)).future())))
return wrapper
@ -119,11 +119,11 @@ class MockMonitor:
return resource_pb2.RegisterResourceResponse(urn=urn, id=id_, object=obj_proto)
def RegisterResourceOutputs(self, request):
#pylint: disable=unused-argument
# pylint: disable=unused-argument
return empty_pb2.Empty()
def SupportsFeature(self, request):
#pylint: disable=unused-argument
# pylint: disable=unused-argument
return type('SupportsFeatureResponse', (object,), {'hasSupport' : True})

View file

@ -25,11 +25,8 @@ from ..runtime.proto import resource_pb2
from .rpc_manager import RPC_MANAGER
from ..metadata import get_project, get_stack
from ..output import Output
if TYPE_CHECKING:
from .. import Resource, ResourceOptions, CustomResource
from ..output import Inputs
from .. import Resource, ResourceOptions, CustomResource, Inputs, Output
class ResourceResolverOperations(NamedTuple):
@ -75,6 +72,7 @@ async def prepare_resource(res: 'Resource',
custom: bool,
props: 'Inputs',
opts: Optional['ResourceOptions']) -> ResourceResolverOperations:
from .. import Output # pylint: disable=import-outside-toplevel
log.debug(f"resource {props} preparing to wait for dependencies")
# Before we can proceed, all our dependencies must be finished.
explicit_urn_dependencies = []
@ -141,11 +139,11 @@ async def prepare_resource(res: 'Resource',
class _ResourceResult(NamedTuple):
urn: Output[str]
urn: 'Output[str]'
"""
The URN of the resource.
"""
id: Optional[Output[str]] = None
id: Optional['Output[str]'] = None
"""
The id of the resource, if it's a CustomResource, otherwise None.
"""
@ -154,6 +152,7 @@ class _ResourceResult(NamedTuple):
# pylint: disable=too-many-locals,too-many-statements
def _read_resource(res: 'CustomResource', ty: str, name: str, props: 'Inputs', opts: 'ResourceOptions') -> _ResourceResult:
from .. import Output # pylint: disable=import-outside-toplevel
if opts.id is None:
raise Exception(
"Cannot read resource whose options are lacking an ID value")
@ -173,7 +172,7 @@ def _read_resource(res: 'CustomResource', ty: str, name: str, props: 'Inputs', o
urn_secret.set_result(False)
resolve_urn = urn_future.set_result
resolve_urn_exn = urn_future.set_exception
result_urn = known_types.new_output({res}, urn_future, urn_known, urn_secret)
result_urn = Output({res}, urn_future, urn_known, urn_secret)
# Furthermore, since resources being Read must always be custom resources (enforced in the
# Resource constructor), we'll need to set up the ID field which will be populated at the end of
@ -185,7 +184,7 @@ def _read_resource(res: 'CustomResource', ty: str, name: str, props: 'Inputs', o
resolve_value: asyncio.Future[Any] = asyncio.Future()
resolve_perform_apply: asyncio.Future[bool] = asyncio.Future()
resolve_secret: asyncio.Future[bool] = asyncio.Future()
result_id = known_types.new_output(
result_id = Output(
{res}, resolve_value, resolve_perform_apply, resolve_secret)
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):
@ -236,7 +235,7 @@ def _read_resource(res: 'CustomResource', ty: str, name: str, props: 'Inputs', o
additionalSecretOutputs=additional_secret_outputs,
)
from ..resource import create_urn # pylint: disable=import-outside-toplevel
from ..resource import create_urn # pylint: disable=import-outside-toplevel
mock_urn = await create_urn(name, ty, resolver.parent_urn).future()
def do_rpc_call():
@ -293,6 +292,7 @@ def _register_resource(res: 'Resource',
opts: Optional['ResourceOptions']) -> _ResourceResult:
log.debug(f"registering resource: ty={ty}, name={name}, custom={custom}")
monitor = settings.get_monitor()
from .. import Output # pylint: disable=import-outside-toplevel
# Prepare the resource.
@ -307,7 +307,7 @@ def _register_resource(res: 'Resource',
urn_secret.set_result(False)
resolve_urn = urn_future.set_result
resolve_urn_exn = urn_future.set_exception
result_urn = known_types.new_output({res}, urn_future, urn_known, urn_secret)
result_urn = Output({res}, urn_future, urn_known, urn_secret)
# If a custom resource, make room for the ID property.
result_id = None
@ -317,7 +317,7 @@ def _register_resource(res: 'Resource',
resolve_value: asyncio.Future[Any] = asyncio.Future()
resolve_perform_apply: asyncio.Future[bool] = asyncio.Future()
resolve_secret: asyncio.Future[bool] = asyncio.Future()
result_id = known_types.new_output(
result_id = Output(
{res}, resolve_value, resolve_perform_apply, resolve_secret)
def do_resolve(value: Any, perform_apply: bool, exn: Optional[Exception]):

View file

@ -211,31 +211,32 @@ def deserialize_properties(props_struct: struct_pb2.Struct, keep_unknowns: Optio
# We assume that we are deserializing properties that we got from a Resource RPC endpoint,
# which has type `Struct` in our gRPC proto definition.
if _special_sig_key in props_struct:
from .. import FileAsset, StringAsset, RemoteAsset, AssetArchive, FileArchive, RemoteArchive # pylint: disable=import-outside-toplevel
if props_struct[_special_sig_key] == _special_asset_sig:
# This is an asset. Re-hydrate this object into an Asset.
if "path" in props_struct:
return known_types.new_file_asset(props_struct["path"])
return FileAsset(props_struct["path"])
if "text" in props_struct:
return known_types.new_string_asset(props_struct["text"])
return StringAsset(props_struct["text"])
if "uri" in props_struct:
return known_types.new_remote_asset(props_struct["uri"])
raise AssertionError("Invalid asset encountered when unmarshaling resource property")
return RemoteAsset(props_struct["uri"])
raise AssertionError("Invalid asset encountered when unmarshalling resource property")
if props_struct[_special_sig_key] == _special_archive_sig:
# This is an archive. Re-hydrate this object into an Archive.
if "assets" in props_struct:
return known_types.new_asset_archive(deserialize_property(props_struct["assets"]))
return AssetArchive(deserialize_property(props_struct["assets"]))
if "path" in props_struct:
return known_types.new_file_archive(props_struct["path"])
return FileArchive(props_struct["path"])
if "uri" in props_struct:
return known_types.new_remote_archive(props_struct["uri"])
raise AssertionError("Invalid archive encountered when unmarshaling resource property")
return RemoteArchive(props_struct["uri"])
raise AssertionError("Invalid archive encountered when unmarshalling resource property")
if props_struct[_special_sig_key] == _special_secret_sig:
return {
_special_sig_key: _special_secret_sig,
"value": deserialize_property(props_struct["value"])
}
raise AssertionError("Unrecognized signature when unmarshaling resource property")
raise AssertionError("Unrecognized signature when unmarshalling resource property")
# Struct is duck-typed like a dictionary, so we can iterate over it in the normal ways. Note
# that if the struct had any secret properties, we push the secretness of the object up to us
@ -269,8 +270,9 @@ def deserialize_property(value: Any, keep_unknowns: Optional[bool] = None) -> An
Deserializes a single protobuf value (either `Struct` or `ListValue`) into idiomatic
Python values.
"""
from ..output import Unknown # pylint: disable=import-outside-toplevel
if value == UNKNOWN:
return known_types.new_unknown() if settings.is_dry_run() or keep_unknowns else None
return Unknown() if settings.is_dry_run() or keep_unknowns else None
# ListValues are projected to lists
if isinstance(value, struct_pb2.ListValue):
@ -321,6 +323,7 @@ result in the exception being re-thrown.
def transfer_properties(res: 'Resource', props: 'Inputs') -> Dict[str, Resolver]:
from .. import Output # pylint: disable=import-outside-toplevel
resolvers: Dict[str, Resolver] = {}
for name in props.keys():
if name in ["id", "urn"]:
@ -354,7 +357,7 @@ def transfer_properties(res: 'Resource', props: 'Inputs') -> Dict[str, Resolver]
# using res.translate_output_property and then use *that* name to index into the resolvers table.
log.debug(f"adding resolver {name}")
resolvers[name] = functools.partial(do_resolve, resolve_value, resolve_is_known, resolve_is_secret)
res.__setattr__(name, known_types.new_output({res}, resolve_value, resolve_is_known, resolve_is_secret))
res.__setattr__(name, Output({res}, resolve_value, resolve_is_known, resolve_is_secret))
return resolvers

View file

@ -18,16 +18,17 @@ Support for automatic stack components.
import asyncio
import collections
from inspect import isawaitable
from typing import Callable, Any, Dict, List
from typing import Callable, Any, Dict, List, TYPE_CHECKING
from ..resource import ComponentResource, Resource, ResourceTransformation
from .settings import get_project, get_stack, get_root_resource, is_dry_run, set_root_resource
from .rpc_manager import RPC_MANAGER
from .sync_await import _all_tasks, _get_current_task
from .. import log
from . import known_types
from ..output import Output
if TYPE_CHECKING:
from .. import Output
async def run_pulumi_func(func: Callable):
try:
@ -72,6 +73,7 @@ async def run_pulumi_func(func: Callable):
if RPC_MANAGER.unhandled_exception is not None:
raise RPC_MANAGER.unhandled_exception.with_traceback(RPC_MANAGER.exception_traceback)
async def run_in_stack(func: Callable):
"""
Run the given function inside of a new stack resource. This ensures that any stack export calls
@ -81,7 +83,6 @@ async def run_in_stack(func: Callable):
await run_pulumi_func(lambda: Stack(func))
@known_types.stack
class Stack(ComponentResource):
"""
A synthetic stack component that automatically parents resources as the program runs.
@ -113,6 +114,7 @@ class Stack(ComponentResource):
"""
self.outputs[name] = value
# Note: we use a List here instead of a set as many objects are unhashable. This is inefficient,
# but python seems to offer no alternative.
def massage(attr: Any, seen: List[Any]):
@ -123,9 +125,9 @@ def massage(attr: Any, seen: List[Any]):
lists or dictionaries as appropriate. In general, iterable things are turned into lists, and
dictionary-like things are turned into dictionaries.
"""
from .. import Output # pylint: disable=import-outside-toplevel
# Basic primitive types (numbers, booleans, strings, etc.) don't need any special handling.
if is_primitive(attr):
return attr
@ -208,6 +210,7 @@ def is_primitive(attr: Any) -> bool:
return True
def register_stack_transformation(t: ResourceTransformation):
"""
Add a transformation to all future resources constructed in this Pulumi stack.

View file

@ -130,6 +130,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7 h1:6pwm8kMQKCmgUg0ZHTm5+/YvRK0s3THD/28+T6/kk4A=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -314,12 +316,16 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=