Skip to content
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

tfsdk: Allow Plan and State SetAttribute to create attribute paths #165

Merged
merged 10 commits into from
Sep 24, 2021

Conversation

bflad
Copy link
Contributor

@bflad bflad commented Sep 16, 2021

Closes #148

@bflad bflad added the bug Something isn't working label Sep 16, 2021
@bflad bflad added this to the v0.4.0 milestone Sep 16, 2021
@bflad bflad requested a review from a team September 16, 2021 21:33
bflad added a commit that referenced this pull request Sep 16, 2021
tfsdk/plan.go Show resolved Hide resolved
}

var parentTfVal tftypes.Value
parentPath := path.WithoutLastStep()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the root, and has no parent, parentPath will be nil.

tfsdk/plan.go Outdated
Comment on lines 227 to 237
if parentValue.Equal(tftypes.NewValue(tftypes.Object{}, nil)) {
// NewValue will panic if required attributes are missing in the
// tftypes.Object.
vals := map[string]tftypes.Value{}
for name, t := range parentTfType.(tftypes.Object).AttributeTypes {
vals[name] = tftypes.NewValue(t, nil)
}
parentValue = tftypes.NewValue(parentTfType, vals)
} else if parentValue.Equal(tftypes.Value{}) {
parentValue = tftypes.NewValue(parentTfType, nil)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if parentValue.Equal(tftypes.NewValue(tftypes.Object{}, nil)) {
// NewValue will panic if required attributes are missing in the
// tftypes.Object.
vals := map[string]tftypes.Value{}
for name, t := range parentTfType.(tftypes.Object).AttributeTypes {
vals[name] = tftypes.NewValue(t, nil)
}
parentValue = tftypes.NewValue(parentTfType, vals)
} else if parentValue.Equal(tftypes.Value{}) {
parentValue = tftypes.NewValue(parentTfType, nil)
}
if parentValue.IsNull() || !parentValue.IsKnown() {
var elementVal interface{}
if !parentValue.IsKnown() {
elementVal = tftypes.UnknownValue
}
switch {
case parentValue.Type().Is(tftypes.Object{}):
vals := map[string]tftypes.Value{}
for name, t := range parentTfType.(tftypes.Object).AttributeTypes {
vals[name] = tftypes.NewValue(t, elementVal)
}
parentValue = tftypes.NewValue(parentTfType, vals)
case parentValue.Type().Is(tftypes.List{}), parentValue.Type().Is(tftypes.Set{}):
parentValue = tftypes.NewValue(parentTfType, []tftypes.Value{})
case parentValue.Type().Is(tftypes.Map{}):
parentValue = tftypes.NewValue(parentTfType, map[string]tftypes.Value{})
case parentValue.Type().Is(tftypes.Tuple{}):
vals := []tftypes.Value{}
for _, t := range parentTfType.(tftypes.Tuple).ElementTypes {
vals = append(vals, tftypes.NewValue(t, elementVal)
}
parentValue = tftypes.NewValue(parentTfType, vals)
default:
panic("cry about it I guess?")
}
}

Also maybe we want this as a helper function? idk

Makes it more unit testable, at least.

@bflad
Copy link
Contributor Author

bflad commented Sep 21, 2021

Hopefully this is much more understandable now. Please let me know if you want anything else adjusted.

@bflad bflad removed their assignment Sep 21, 2021
tfsdk/plan.go Show resolved Hide resolved
tfsdk/schema.go Show resolved Hide resolved
tfsdk/plan.go Show resolved Hide resolved
return false, diags
}

return len(remaining.Steps()) == 0, diags
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a request for a change, just noting here that this shouldn't be necessary, it should always be true when there's no error. I think it's good to check it anyways, just wanted to surface that context on WalkAttributePath.

tfsdk/schema.go Show resolved Hide resolved
// children. List, Map, and Set are created with empty elements.
//
// The parentType parameter ensures that the value will match the current
// schema, not what is currently stored in the parentValue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would actually like this to actively not be the case; the schema represents type constraints (could be DynamicPseudoType, could be an object with optional attributes) but the value represents a concrete type (any non-DynamicPseudoType type, could be an object without optional attributes, respectively). So when modifying a value, I think we want to stick with the value's interpretation of what the type is supposed to be, not the schema's, so the value can stay internally consistent and match its type. Technically, it may even panic in situations where the value and type of the value start deviating.

// Lists can only have the next element added according to the current length.
//
// The parentType parameter ensures that the value will match the current
// schema, not what is currently stored in the parentValue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here.

parentPath,
"Value Conversion Error",
"An unexpected error was encountered trying to create a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+
fmt.Sprintf("Unknown parent type %T to create value.", parentValue.Type()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fmt.Sprintf("Unknown parent type %T to create value.", parentValue.Type()),
fmt.Sprintf("Unknown parent type %s to create value.", parentValue.Type()),

This will give us a friendlier output of what the type actually is semantically, not what the Go implementation of it is.

}),
Schema: Schema{
Attributes: map[string]Attribute{
"test": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should appear in Raw as nil, I believe?

}),
Schema: Schema{
Attributes: map[string]Attribute{
"tags": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, these attributes would be in the Plan's Raw, set to null, not omitted from it.

…eturned in SetAttribute handling

Previously:

```
--- FAIL: TestPlanSetAttribute (0.01s)
    --- FAIL: TestPlanSetAttribute/write-List-AttrTypeWithValidateWarning-Element (0.00s)
        plan_test.go:2949: unexpected diagnostics (+wanted, -got):   diag.Diagnostics(
            -   nil,
            +   {
            +           diag.AttributeWarningDiagnostic{
            +                   WarningDiagnostic: diag.WarningDiagnostic{detail: "This is a warning.", summary: "Warning Diagnostic"},
            +                   path:              s`AttributeName("test")`,
            +           },
            +   },
              )
    --- FAIL: TestPlanSetAttribute/write-Map-AttrTypeWithValidateWarning-Element (0.00s)
        plan_test.go:2949: unexpected diagnostics (+wanted, -got):   diag.Diagnostics(
            -   nil,
            +   {
            +           diag.AttributeWarningDiagnostic{
            +                   WarningDiagnostic: diag.WarningDiagnostic{detail: "This is a warning.", summary: "Warning Diagnostic"},
            +                   path:              s`AttributeName("test")`,
            +           },
            +   },
              )
    --- FAIL: TestPlanSetAttribute/write-Set-AttrTypeWithValidateWarning-Element (0.00s)
        plan_test.go:2949: unexpected diagnostics (+wanted, -got):   diag.Diagnostics(
            -   nil,
            +   {
            +           diag.AttributeWarningDiagnostic{
            +                   WarningDiagnostic: diag.WarningDiagnostic{detail: "This is a warning.", summary: "Warning Diagnostic"},
            +                   path:              s`AttributeName("test")`,
            +           },
            +   },
              )
--- FAIL: TestStateSetAttribute (0.01s)
    --- FAIL: TestStateSetAttribute/write-Map-AttrTypeWithValidateWarning-Element (0.00s)
        state_test.go:4349: unexpected diagnostics (+wanted, -got):   diag.Diagnostics(
            -   nil,
            +   {
            +           diag.AttributeWarningDiagnostic{
            +                   WarningDiagnostic: diag.WarningDiagnostic{detail: "This is a warning.", summary: "Warning Diagnostic"},
            +                   path:              s`AttributeName("test")`,
            +           },
            +   },
              )
    --- FAIL: TestStateSetAttribute/write-List-AttrTypeWithValidateWarning-Element (0.00s)
        state_test.go:4349: unexpected diagnostics (+wanted, -got):   diag.Diagnostics(
            -   nil,
            +   {
            +           diag.AttributeWarningDiagnostic{
            +                   WarningDiagnostic: diag.WarningDiagnostic{detail: "This is a warning.", summary: "Warning Diagnostic"},
            +                   path:              s`AttributeName("test")`,
            +           },
            +   },
              )
    --- FAIL: TestStateSetAttribute/write-Set-AttrTypeWithValidateWarning-Element (0.00s)
        state_test.go:4349: unexpected diagnostics (+wanted, -got):   diag.Diagnostics(
            -   nil,
            +   {
            +           diag.AttributeWarningDiagnostic{
            +                   WarningDiagnostic: diag.WarningDiagnostic{detail: "This is a warning.", summary: "Warning Diagnostic"},
            +                   path:              s`AttributeName("test")`,
            +           },
            +   },
              )
FAIL
```
Copy link
Contributor

@paddycarver paddycarver left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's ship it, great work 👍 🚀

@bflad bflad merged commit afbf734 into main Sep 24, 2021
@bflad bflad deleted the bflad-b-SetAttribute-writes branch September 24, 2021 01:12
@github-actions
Copy link

I'm going to lock this pull request because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 24, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SetAttribute on Missing Attribute Path Silently Fails Without Update
2 participants