-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't flatten outputs to top level for InputList and InputMap #449
Changes from all commits
ebb94d1
c5e6278
de4c3b4
d2476b5
4fe12ab
fe2d32f
e82f9b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
component: sdk | ||
kind: Improvements | ||
body: InputMap and InputList no longer flatten nested unknowns/secrets to apply to | ||
the whole object. | ||
time: 2025-01-29T14:14:44.986465684Z | ||
custom: | ||
PR: "449" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,18 +46,38 @@ namespace Pulumi | |
/// </summary> | ||
public sealed class InputList<T> : Input<ImmutableArray<T>>, IEnumerable, IAsyncEnumerable<Input<T>> | ||
{ | ||
public InputList() : this(Output.Create(ImmutableArray<T>.Empty)) | ||
Input<ImmutableArray<Input<T>>> _inputValue; | ||
/// <summary> | ||
/// InputList externally has to behave as an <c>Input{ImmutableArray{T}}</c>, but we actually want to | ||
/// keep nested Input/Output values separate, so that we can serialise the overall list shape even if one of the | ||
/// inner elements is an unknown value. | ||
/// | ||
/// To do that we keep a separate value of the form <c>Input{ImmutableArray{Input{T}}}</c>/> which each | ||
/// time we set syncs the flattened value to the base <c>Input{ImmutableArray{T}}</c>. | ||
/// </summary> | ||
Input<ImmutableArray<Input<T>>> Value | ||
{ | ||
get => _inputValue; | ||
set | ||
{ | ||
_inputValue = value; | ||
_outputValue = _inputValue.Apply(inputs => Output.All(inputs)); | ||
} | ||
} | ||
|
||
public InputList() : this(ImmutableArray<Input<T>>.Empty) | ||
{ | ||
} | ||
|
||
private InputList(Output<ImmutableArray<T>> values) | ||
: base(values) | ||
private InputList(Input<ImmutableArray<Input<T>>> values) | ||
: base(values.Apply(values => Output.All(values))) | ||
{ | ||
_inputValue = values; | ||
} | ||
|
||
public void Add(params Input<T>[] inputs) | ||
{ | ||
_outputValue = Concat(inputs); | ||
Value = Concat(inputs).Value; | ||
} | ||
|
||
/// <summary> | ||
|
@@ -70,18 +90,26 @@ public void Add(InputList<T> inputs) | |
|
||
public void AddRange(InputList<T> inputs) | ||
{ | ||
_outputValue = Concat(inputs); | ||
Value = Concat(inputs).Value; | ||
} | ||
|
||
/// <summary> | ||
/// Concatenates the values in this list with the values in <paramref name="other"/>, | ||
/// returning the concatenated sequence in a new <see cref="InputList{T}"/>. | ||
/// </summary> | ||
public InputList<T> Concat(InputList<T> other) | ||
=> Output.Concat(_outputValue, other._outputValue); | ||
{ | ||
var list = new InputList<T>(); | ||
list.Value = Output.Tuple(Value, other.Value).Apply(t => | ||
{ | ||
var (first, second) = t; | ||
return first.AddRange(second); | ||
}); | ||
return list; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeh, if we just Concat'd the whole thing we'd end up lifting all the sub-element secret/known bits to the top level. |
||
} | ||
|
||
internal InputList<T> Clone() | ||
=> new InputList<T>(_outputValue); | ||
=> new InputList<T>(Value); | ||
|
||
#region construct from unary | ||
|
||
|
@@ -131,7 +159,7 @@ public static implicit operator InputList<T>(ImmutableArray<Output<T>> values) | |
=> values.SelectAsArray(v => (Input<T>)v); | ||
|
||
public static implicit operator InputList<T>(ImmutableArray<Input<T>> values) | ||
=> Output.All(values); | ||
=> new InputList<T>(values); | ||
|
||
#endregion | ||
|
||
|
@@ -147,7 +175,15 @@ public static implicit operator InputList<T>(Output<IEnumerable<T>> values) | |
=> values.Apply(ImmutableArray.CreateRange); | ||
|
||
public static implicit operator InputList<T>(Output<ImmutableArray<T>> values) | ||
=> new InputList<T>(values); | ||
=> new InputList<T>(values.Apply(values => | ||
{ | ||
var builder = ImmutableArray.CreateBuilder<Input<T>>(values.Length); | ||
foreach (var value in values) | ||
{ | ||
builder.Add(value); | ||
} | ||
return builder.MoveToImmutable(); | ||
})); | ||
|
||
#endregion | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,20 +42,56 @@ namespace Pulumi | |
/// </summary> | ||
public sealed class InputMap<V> : Input<ImmutableDictionary<string, V>>, IEnumerable, IAsyncEnumerable<Input<KeyValuePair<string, V>>> | ||
{ | ||
public InputMap() : this(Output.Create(ImmutableDictionary<string, V>.Empty)) | ||
private static Input<ImmutableDictionary<string, V>> Flatten(Input<ImmutableDictionary<string, Input<V>>> inputs) | ||
{ | ||
return inputs.Apply(inputs => | ||
{ | ||
var list = inputs.Select(kv => kv.Value.Apply(value => KeyValuePair.Create(kv.Key, value))); | ||
return Output.All(list).Apply(kvs => | ||
{ | ||
var result = ImmutableDictionary.CreateBuilder<string, V>(); | ||
foreach (var (k, v) in kvs) | ||
{ | ||
result[k] = v; | ||
} | ||
return result.ToImmutable(); | ||
}); | ||
}); | ||
} | ||
|
||
Input<ImmutableDictionary<string, Input<V>>> _inputValue; | ||
/// <summary> | ||
/// InputMap externally has to behave as an <c>Input{ImmutableDictionary{string, T}}</c>, but we actually | ||
/// want to keep nested Input/Output values separate, so that we can serialise the overall map shape even if | ||
/// one of the inner elements is an unknown value. | ||
/// | ||
/// To do that we keep a separate value of the form <c>Input{ImmutableDictionary{string, Input{T}}}</c> | ||
/// which each time we set syncs the flattened value to the base <c>Input{ImmutableDictionary{string, | ||
/// T}}</c>. | ||
/// </summary> | ||
Input<ImmutableDictionary<string, Input<V>>> Value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comments as for list around doc comments/use of |
||
{ | ||
get => _inputValue; | ||
set | ||
{ | ||
_inputValue = value; | ||
_outputValue = Flatten(_inputValue); | ||
} | ||
} | ||
|
||
public InputMap() : this(ImmutableDictionary<string, Input<V>>.Empty) | ||
{ | ||
} | ||
|
||
private InputMap(Output<ImmutableDictionary<string, V>> values) | ||
: base(values) | ||
private InputMap(Input<ImmutableDictionary<string, Input<V>>> values) | ||
: base(Flatten(values)) | ||
{ | ||
_inputValue = values; | ||
} | ||
|
||
public void Add(string key, Input<V> value) | ||
{ | ||
var inputDictionary = (Input<ImmutableDictionary<string, V>>)_outputValue; | ||
_outputValue = Output.Tuple(inputDictionary, value) | ||
.Apply(x => x.Item1.Add(key, x.Item2)); | ||
Value = Value.Apply(self => self.Add(key, value)); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -68,8 +104,7 @@ public void Add(InputMap<V> values) | |
|
||
public void AddRange(InputMap<V> values) | ||
{ | ||
var inputDictionary = (Input<ImmutableDictionary<string, V>>)_outputValue; | ||
_outputValue = Output.Tuple(inputDictionary, values) | ||
Value = Output.Tuple(Value, values.Value) | ||
.Apply(x => x.Item1.AddRange(x.Item2)); | ||
} | ||
|
||
|
@@ -91,16 +126,18 @@ public Input<V> this[string key] | |
/// both input maps.</returns> | ||
public static InputMap<V> Merge(InputMap<V> first, InputMap<V> second) | ||
{ | ||
var output = Output.Tuple(first._outputValue, second._outputValue) | ||
var output = Output.Tuple(first.Value, second.Value) | ||
.Apply(dicts => | ||
{ | ||
var result = new Dictionary<string, V>(dicts.Item1); | ||
var builder = ImmutableDictionary.CreateBuilder<string, Input<V>>(); | ||
foreach (var (k, v) in dicts.Item1) | ||
builder[k] = v; | ||
// Overwrite keys if duplicates are found | ||
foreach (var (k, v) in dicts.Item2) | ||
result[k] = v; | ||
return result; | ||
builder[k] = v; | ||
return builder.ToImmutable(); | ||
}); | ||
return output; | ||
return new InputMap<V>(output); | ||
} | ||
|
||
#region construct from dictionary types | ||
|
@@ -118,7 +155,15 @@ public static implicit operator InputMap<V>(Output<IDictionary<string, V>> value | |
=> values.Apply(ImmutableDictionary.CreateRange); | ||
|
||
public static implicit operator InputMap<V>(Output<ImmutableDictionary<string, V>> values) | ||
=> new InputMap<V>(values); | ||
=> new InputMap<V>(values.Apply(values => | ||
{ | ||
var builder = ImmutableDictionary.CreateBuilder<string, Input<V>>(); | ||
foreach (var value in values) | ||
{ | ||
builder.Add(value.Key, value.Value); | ||
} | ||
return builder.ToImmutable(); | ||
})); | ||
|
||
#endregion | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reckon it's worth extending the doc comment to include some of the awesome context you put in the PR description? Or perhaps using that as a comment for the
Value
property/_inputValue
instance variable if you think it's better kept private/internal?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On Value for sure, not sure it can be sensibly added to the user level description.