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

Implement YAML roundtripping, comment preserving edits #11456

Merged
merged 7 commits into from Dec 13, 2022

Conversation

AaronFriel
Copy link
Member

@AaronFriel AaronFriel commented Nov 23, 2022

By caching a copy of the raw bytes of the original loaded document - project, policy project, or stack - on save we:

  • Unmarshal the original bytes to a YAML AST
  • Recursively edit nodes of the document to match the values of the value we're saving, preserving trivia
  • Marshal the modified AST to []byte

Closes #423, as we don't support comments in JSON presently (not using "JSON5")

Closes #5235

Simplifies work done in #10797 and #10437 which used the previous yamlutil package to edit AST nodes directly.

Automation API

Integration with automation API requires that operations either perform a "read-modify-write" in a single operation, or use a side channel to allow subsequent "read" (select stack?) and "write" operations to keep state of the original file.

However I don't expect this to be needed. We don't have a reason to believe automation API users maintain comments in particular stack files, and doing so is already unsupported in automation scenarios. The Pulumi Service does not persist raw config files, thus pulumi config refresh and CreateOrSelectStack create config files without comments.

n.b.: If you test this, due to the new behavior of this PR, pulumi config refresh will persist comments if there is an existing config file. I would expect automation API using local file workspaces to behave similarly. When using these tools to create a config file from the last deployment's definition, the file is created restored without comments.

Tradeoffs

I believe that this closes #423 in spirit, but doesn't strictly implement round-tripping. There are a couple reasons for this.

Marshaller interface and round-tripping simplification decisions

We support unmarshaling "simplified" YAML, including allowing:

runtime: yaml # this unmarshals to a struct, not a scalar value
config:
  someKey: "someScalar" # likewise

These simplifications make it much more difficult, the Marshal operation would also need context of decisions made when unmarshaling to ensure a bijection. Otherwise we may save one of these as the other:

runtime:
  name: yaml
runtime: yaml

This could be considered a feature, as it enforces a style guide - the decisions implemented in our custom Marshaller implementations become the style enforced on save.

That said, I believe the new Edit API while not round-trippable, does reach a fixed point after one save through the new API. Once saved with this API, subsequent loads & saves without changes are idempotent.

Indentation

The YAML library uses a global indentation when marshaling values to YAML, as opposed to indentation being a property of AST nodes. This means we may lose other indentation.

@pulumi-bot
Copy link
Contributor

pulumi-bot commented Nov 23, 2022

Changelog

[uncommitted] (2022-12-12)

Features

  • [cli/{config,new,package}] Preserve comments on editing of project and config files.
    #11456

Copy link
Member

@iwahbe iwahbe left a comment

Choose a reason for hiding this comment

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

Would it be possible to piggyback an integration test for pulumi config set for this change?

Looks good too me.

Comment on lines 39 to 47
var b bytes.Buffer
yamlEncoder := yaml.NewEncoder(&b)
yamlEncoder.SetIndent(2)
err = yamlEncoder.Encode(&newValue)
if err != nil {
return nil, fmt.Errorf("error editing value %#v: %w", newValue, err)
}

return b.Bytes(), nil
Copy link
Member

Choose a reason for hiding this comment

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

If there was a way to avoid repeating this in sdk/go/common/encoding/marshal.go, that would be great.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exported a two space indendation YamlEncode and refactored.

@AaronFriel
Copy link
Member Author

@iwahbe re: testing. Yeah I think so - we could modify an integration test to start with a file that has some config and comments. I'll do that.

@AaronFriel AaronFriel force-pushed the friel/yaml-roundtrip branch 2 times, most recently from 27c750e to 166a652 Compare December 10, 2022 20:38
@AaronFriel
Copy link
Member Author

AaronFriel commented Dec 10, 2022

@Frassle @justinvp @iwahbe Added tests here: https://github.com/pulumi/pulumi/blob/friel/yaml-roundtrip/tests/roundtrip_test.go

I'd like the first test to use pulumi config set --project when that is implemented. For now, it loads and saves the project in the same manner as the CLI does.

@AaronFriel AaronFriel force-pushed the friel/yaml-roundtrip branch 2 times, most recently from a5c3d75 to 39a108e Compare December 10, 2022 21:03
@AaronFriel AaronFriel force-pushed the friel/yaml-roundtrip branch 3 times, most recently from 176cf85 to 12a8743 Compare December 12, 2022 00:52
Copy link
Member

@justinvp justinvp left a comment

Choose a reason for hiding this comment

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

🎉 LGTM

@AaronFriel
Copy link
Member Author

bors merge

bors bot added a commit that referenced this pull request Dec 13, 2022
11456: Implement YAML roundtripping, comment preserving edits r=AaronFriel a=AaronFriel

By caching a copy of the raw bytes of the original loaded document - project, policy project, or stack - on save we:

* Unmarshal the original bytes to a YAML AST
* Recursively edit nodes of the document to match the values of the value we're saving, preserving trivia
* Marshal the modified AST to []byte

Closes #423, as we don't support comments in JSON presently (not using "JSON5")

Closes #5235

Simplifies work done in #10797 and #10437 which used the previous `yamlutil` package to edit AST nodes directly.

# Automation API

Integration with automation API requires that operations either perform a "read-modify-write" in a single operation, or use a side channel to allow subsequent "read" (select stack?) and "write" operations to keep state of the original file.

However I don't expect this to be needed. We don't have a reason to believe automation API users maintain comments in particular stack files, and doing so is already unsupported in automation scenarios. The Pulumi Service does not persist raw config files, thus `pulumi config refresh` and `CreateOrSelectStack` create config files without comments.

n.b.: If you test this, due to the new behavior of this PR, `pulumi config refresh` will persist comments if there is an existing config file. I would expect automation API using local file workspaces to behave similarly. When using these tools to create a config file from the last deployment's definition, the file is created restored without comments.

# Tradeoffs

I believe that this closes #423 in spirit, but doesn't strictly implement round-tripping. There are a couple reasons for this.

## Marshaller interface and round-tripping simplification decisions

We support unmarshaling "simplified" YAML, including allowing:

```yaml
runtime: yaml # this unmarshals to a struct, not a scalar value
config:
  someKey: "someScalar" # likewise
```

These simplifications make it much more difficult, the `Marshal` operation would _also_ need context of decisions made when unmarshaling to ensure a bijection. Otherwise we may save one of these as the other:

```yaml
runtime:
  name: yaml
```

```yaml
runtime: yaml
```

This could be considered a feature, as it enforces a style guide - the decisions implemented in our custom `Marshaller` implementations become the style enforced on save.

That said, I believe the new `Edit` API while not round-trippable, does reach a fixed point after one save through the new API. Once saved with this API, subsequent loads & saves without changes are idempotent.

## Indentation

The YAML library uses a global indentation when marshaling values to YAML, as opposed to indentation being a property of AST nodes. This means we may lose other indentation.

Co-authored-by: Aaron Friel <mayreply@aaronfriel.com>
@bors
Copy link
Contributor

bors bot commented Dec 13, 2022

Build failed:

@AaronFriel
Copy link
Member Author

bors retry

@bors
Copy link
Contributor

bors bot commented Dec 13, 2022

Build succeeded:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pulumi Should retain comments in Stack Config Support full fidelity round-tripping of Pulumi.[yaml|json]
4 participants