Pass dependency information to the engine

Dependency information can come from one of two places, an explicit
set of resources that you depend on (via the ResourceOptions struct)
and dependencies we infer from output tracking.
This commit is contained in:
Matt Ellis 2018-06-22 16:53:21 -07:00
parent 66ef108516
commit 9de9ad2f97
5 changed files with 54 additions and 22 deletions

View file

@ -23,7 +23,8 @@ namespace Pulumi {
} else if (resp.IsFaulted) {
m_IdCompletionSoruce.SetException(resp.Exception);
} else {
m_IdCompletionSoruce.SetResult(new OutputState<string>(resp.Result.Id, !string.IsNullOrEmpty(resp.Result.Id)));
Serilog.Log.Debug("Setting id to {id} for {urn} with dependency {this}", resp.Result.Id, resp.Result.Urn, this);
m_IdCompletionSoruce.SetResult(new OutputState<string>(resp.Result.Id, !string.IsNullOrEmpty(resp.Result.Id), this));
}
}
}

View file

@ -35,11 +35,14 @@ namespace Pulumi {
public async Task<OutputState<object>> GetValueAsOutputStateAsync() {
if (m_task != null) {
return new OutputState<object>(await m_task, true);
return new OutputState<object>(await m_task, true, Array.Empty<Resource>());
} else if (m_output != null) {
return await ((IOutput)m_output).GetOutputStateAsync();
} else {
return new OutputState<object>(m_rawValue, true);
// If the underlying value is a resource, ensure we flow the resource as a dependency in the synthetic output state.
// TODO(ellismg): Doing this here feels wrong for some reason.
Resource r = m_rawValue as Resource;
return new OutputState<object>(m_rawValue, true, r != null ? new Resource[] { r } : Array.Empty<Resource>());
}
}
}

View file

@ -8,9 +8,13 @@ namespace Pulumi {
public T Value { get; private set; }
public bool IsKnown { get; private set; }
public OutputState(T value, bool isKnown) {
public Resource[] DependsOn { get; private set; }
public OutputState(T value, bool isKnown, params Resource[] dependsOn) {
Serilog.Log.Debug("Creating new OutputState {value} {isKnown} {dependsOn}", value, isKnown, dependsOn);
Value = value;
IsKnown = isKnown;
DependsOn = dependsOn;
}
}
interface IOutput {
@ -26,7 +30,7 @@ namespace Pulumi {
async Task<OutputState<object>> IOutput.GetOutputStateAsync() {
var resolvedState = await m_stateTask;
return new OutputState<object>(resolvedState.Value, resolvedState.IsKnown);
return new OutputState<object>(resolvedState.Value, resolvedState.IsKnown, resolvedState.DependsOn);
}
public void Apply(Action<T> fn) {
@ -38,7 +42,8 @@ namespace Pulumi {
public Output<U> Apply<U>(Func<T, U> fn) {
return new Output<U>(this.m_stateTask.ContinueWith(x => new OutputState<U>(
x.Result.IsKnown ? fn(x.Result.Value) : default(U),
x.Result.IsKnown)));
x.Result.IsKnown,
x.Result.DependsOn)));
}
}
}

View file

@ -1,3 +1,4 @@
using Google.Protobuf.Collections;
using Google.Protobuf.WellKnownTypes;
using Pulumirpc;
using System;
@ -25,7 +26,7 @@ namespace Pulumi
} else if (resp.IsFaulted) {
m_UrnCompletionSource.SetException(resp.Exception);
} else {
m_UrnCompletionSource.SetResult(new OutputState<string>(resp.Result.Urn, resp.Result.Urn != null));
m_UrnCompletionSource.SetResult(new OutputState<string>(resp.Result.Urn, resp.Result.Urn != null, this));
}
}
@ -52,19 +53,45 @@ namespace Pulumi
parentUrn = urnOutput.GetOutputStateAsync().ContinueWith(x => (string)x.Result.Value);
}
// Compute the set of dependencies this resource has. This is the union of resources the object explicitly depends on
// with the set of dependencies that any Output that is used as in Input has.
HashSet<string> dependsOnUrns = new HashSet<string>(StringComparer.Ordinal);
// Explicit dependencies.
if (options.DependsOn != null) {
foreach (Resource r in options.DependsOn) {
dependsOnUrns.Add((string)(await ((IOutput)r.Urn).GetOutputStateAsync()).Value);
}
}
// Add any dependeices from any outputs that happend to be used as inputs.
if (properties != null) {
foreach (object o in properties.Values) {
IInput input = o as IInput;
if (input != null) {
foreach (Resource r in (await input.GetValueAsOutputStateAsync()).DependsOn) {
dependsOnUrns.Add((string)(await ((IOutput)r.Urn).GetOutputStateAsync()).Value);
}
}
}
}
foreach(string urn in dependsOnUrns) {
Serilog.Log.Debug("Dependency: {urn}", urn);
}
// Kick off the registration, and when it completes, call the OnResourceRegistrationCompete method which will resolve all the tasks to their values. The fact that we don't
// await here is by design. This method is called by child classes in their constructors, where were do not want to block.
#pragma warning disable 4014
Runtime.Monitor.RegisterResourceAsync(
new RegisterResourceRequest()
{
Type = type,
Name = name,
Custom = custom,
Protect = false,
Object = await SerializeProperties(properties),
Parent = await parentUrn
}).ResponseAsync.ContinueWith((x) => OnResourceRegistrationComplete(x));
RegisterResourceRequest request = new RegisterResourceRequest();
request.Type = type;
request.Name = name;
request.Custom = custom;
request.Protect = options.Protect;
request.Object = await SerializeProperties(properties);
request.Parent = await parentUrn;
request.Dependencies.AddRange(dependsOnUrns);
Runtime.Monitor.RegisterResourceAsync(request).ResponseAsync.ContinueWith((x) => OnResourceRegistrationComplete(x));
#pragma warning restore 4014
}

View file

@ -34,12 +34,8 @@ namespace AWS.S3 {
var fields = resp.Result.Object.Fields;
foreach (var kvp in fields) {
Serilog.Log.Debug("got property {key}", kvp.Key);
}
bool isKnown = fields.ContainsKey("bucketDomainName");
m_BucketDomainNameCompletionSource.SetResult(new OutputState<string>(isKnown ? fields["bucketDomainName"].StringValue : default(string), isKnown));
m_BucketDomainNameCompletionSource.SetResult(new OutputState<string>(isKnown ? fields["bucketDomainName"].StringValue : default(string), isKnown, this));
}
}