Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
11143: [dotnet/auto] allow deserializing complex stack config values r=Zaid-Ajaj a=Zaid-Ajaj

<!--- 
Thanks so much for your contribution! If this is your first time contributing, please ensure that you have read the [CONTRIBUTING](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) documentation.
-->

# Description

<!--- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. -->

Fixes pulumi/pulumi#11107 now instead of failing, we JSON-ify the complex stack config value 

## Checklist

<!--- Please provide details if the checkbox below is to be left unchecked. -->
- [x] I have added tests that prove my fix is effective or that my feature works
<!--- 
User-facing changes require a CHANGELOG entry.
-->
- [x] I have run `make changelog` and committed the `changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the Pulumi Service,
then the service should honor older versions of the CLI where this change would not exist.
You must then bump the API version in /pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi Service API version
  <!-- `@Pulumi` employees: If yes, you must submit corresponding changes in the service repo. -->


Co-authored-by: Zaid Ajaj <zaid.naom@gmail.com>
  • Loading branch information
bors[bot] and Zaid-Ajaj committed Oct 26, 2022
2 parents 6139645 + 365a2e6 commit 69fb6a5
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 45 deletions.
Expand Up @@ -56,25 +56,47 @@ public void CanDeserializeSecureString()
}

[Fact]
public void CannotDeserializeObject()
public void DeserializeObjectWorks()
{
const string json = @"
{
""config"": {
""value"": {
""test"": ""test"",
""nested"": {
""one"": 1,
""two"": true,
""three"": ""three""
}
""hello"": ""world""
}
}
}
";

Assert.Throws<NotSupportedException>(
() => _serializer.DeserializeJson<StackSettings>(json));
var settings = _serializer.DeserializeJson<StackSettings>(json);
Assert.NotNull(settings.Config);
Assert.True(settings!.Config!.ContainsKey("value"));

var value = settings.Config["value"];
Assert.NotNull(value);
Assert.Equal("{\"hello\":\"world\"}", value.Value);
Assert.False(value.IsSecure);
}

[Fact]
public void DeserializeArrayWorks()
{
const string json = @"
{
""config"": {
""value"": [1,2,3,4,5]
}
}
";

var settings = _serializer.DeserializeJson<StackSettings>(json);
Assert.NotNull(settings.Config);
Assert.True(settings!.Config!.ContainsKey("value"));

var value = settings.Config["value"];
Assert.NotNull(value);
Assert.Equal("[1,2,3,4,5]", value.Value);
Assert.False(value.IsSecure);
}

[Fact]
Expand Down
Expand Up @@ -49,20 +49,22 @@ public void CanDeserializeSecureString()
}

[Fact]
public void CannotDeserializeObject()
public void CanDeserializeObject()
{
const string yaml = @"
config:
value:
test: test
nested:
one: 1
two: true
three: three
hello: world
";

Assert.Throws<YamlException>(
() => _serializer.DeserializeYaml<StackSettings>(yaml));
var settings = _serializer.DeserializeYaml<StackSettings>(yaml);
Assert.NotNull(settings.Config);
Assert.True(settings!.Config!.ContainsKey("value"));

var value = settings.Config["value"];
Assert.NotNull(value);
Assert.Equal("{\"hello\":\"world\"}", value.Value);
Assert.False(value.IsSecure);
}

[Fact]
Expand Down
Expand Up @@ -19,22 +19,28 @@ public override StackSettingsConfigValue Read(ref Utf8JsonReader reader, Type ty
return new StackSettingsConfigValue(value, false);
}

// confirm object
if (element.ValueKind != JsonValueKind.Object)
throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}]. Expecting object if not plain string.");

// check if secure string
var securePropertyName = options.PropertyNamingPolicy?.ConvertName("Secure") ?? "Secure";
if (element.TryGetProperty(securePropertyName, out var secureProperty))
// check if the element is an object,
// if it has a single property called "secure" then it is a secret value
// otherwise, serialize the whole object as JSON into the stack settings value
if (element.ValueKind == JsonValueKind.Object)
{
if (secureProperty.ValueKind != JsonValueKind.String)
throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}] as a secure string. Expecting a string secret.");

var secret = secureProperty.GetString();
return new StackSettingsConfigValue(secret, true);
foreach(var property in element.EnumerateObject())
{
if (string.Equals("Secure", property.Name, StringComparison.OrdinalIgnoreCase))
{
var secureValue = property.Value;
if (secureValue.ValueKind != JsonValueKind.String)
{
throw new JsonException($"Unable to deserialize [{typeToConvert.FullName}] as a secure string. Expecting a string secret.");
}

return new StackSettingsConfigValue(secureValue.GetString(), true);
}
}
}

throw new NotSupportedException("Automation API does not currently support deserializing complex objects from stack settings.");
var serializedElement = JsonSerializer.Serialize(element);
return new StackSettingsConfigValue(serializedElement, false);
}

public override void Write(Utf8JsonWriter writer, StackSettingsConfigValue value, JsonSerializerOptions options)
Expand Down
@@ -1,6 +1,8 @@
// Copyright 2016-2021, Pulumi Corporation

using System;
using System.Collections.Generic;
using System.Text.Json;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;
Expand All @@ -22,27 +24,44 @@ public object ReadYaml(IParser parser, Type type)
return new StackSettingsConfigValue(stringValue.Value, false);
}

// confirm it is an object
if (!parser.TryConsume<MappingStart>(out _))
throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting object if not plain string.");
var deserializer = new Deserializer();

// get first property name
if (!parser.TryConsume<Scalar>(out var firstPropertyName))
throw new YamlException($"Unable to deserialize [{type.FullName}]. Expecting first property name inside object.");

// check if secure string
if (string.Equals("Secure", firstPropertyName.Value, StringComparison.OrdinalIgnoreCase))
// check whether it is an object with a single property called "secure"
// this means we have a secret value serialized into the value
if (parser.TryConsume<MappingStart>(out var _))
{
// secure string
if (!parser.TryConsume<Scalar>(out var securePropertyValue))
throw new YamlException($"Unable to deserialize [{type.FullName}] as a secure string. Expecting a string secret.");
var dictionaryFromYaml = new Dictionary<string, object?>();

// in which case, check whether it is a secure value
while (parser.TryConsume<Scalar>(out var firstPropertyName))
{
if (string.Equals("Secure", firstPropertyName.Value, StringComparison.OrdinalIgnoreCase))
{
// secure string
if (!parser.TryConsume<Scalar>(out var securePropertyValue))
throw new YamlException($"Unable to deserialize [{type.FullName}] as a secure string. Expecting a string secret.");

// needs to be 1 mapping end and then return
parser.Require<MappingEnd>();
parser.MoveNext();
return new StackSettingsConfigValue(securePropertyValue.Value, true);
}

// not a secure string, so we need to add first value to the dictionary
dictionaryFromYaml.Add(firstPropertyName.Value, deserializer.Deserialize<object?>(parser));
}

// needs to be 1 mapping end and then return
parser.Require<MappingEnd>();
parser.MoveNext();
return new StackSettingsConfigValue(securePropertyValue.Value, true);
// serialize the dictionary back into the value as JSON
var serializedDictionary = JsonSerializer.Serialize(dictionaryFromYaml);
return new StackSettingsConfigValue(serializedDictionary, false);
}
throw new NotSupportedException("Automation API does not currently support deserializing complex objects from stack settings.");

// for anything else, i.e. arrays, parse the contents as is and serialize it a JSON string
var deserializedFromYaml = deserializer.Deserialize<object?>(parser);
var serializedToJson = JsonSerializer.Serialize(deserializedFromYaml);
return new StackSettingsConfigValue(serializedToJson, false);
}

public void WriteYaml(IEmitter emitter, object? value, Type type)
Expand Down

0 comments on commit 69fb6a5

Please sign in to comment.