Skip to content

Conversation

scottrigby
Copy link
Member

Since #1656 Helm deep-merges multiple values files. However, this also means there is currently no way to delete an existing key that may be incompatible with a newly added one.

This PR addresses option 2 in #1966. My Golang fu is not top notch yet, but that said let me know if this is wrong so I can fix it (PS Thanks to @rabellamy for sending me Go map documentation).

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jul 6, 2017
@k8s-reviewable
Copy link

This change is Reviewable

@thomastaylor312
Copy link
Contributor

@scottrigby I think that you added the right logic in the wrong place. When I specify foo: "null", I get this:

NAME:   awesome-panther
REVISION: 1
RELEASED: Wed Jul  5 21:30:57 2017
CHART: nginx-0.1.0
USER-SUPPLIED VALUES:
{}

COMPUTED VALUES:
foo: bar
image:
  pullPolicy: IfNotPresent
  repository: nginx
  tag: 1.11.0
index: <h1>Hello</h1> <p>This is a test</p>
nodeSelector: {}
podAnnotations: {}
replicaCount: 1
resources: {}
restartPolicy: Never
service:
  annotations: {}
  clusterIP: ""
  externalIPs: []
  loadBalancerIP: ""
  loadBalancerSourceRanges: []
  nodePort: ""
  port: 8888
  type: ClusterIP
sleepyTime: "10"

The function you modified only is merging user supplied values, not the values.yaml file. That merging is done on the tiller side (confusing I know) right here. So I think if you move the logic to there it will work

seh
seh previously requested changes Jul 6, 2017
// If the new value is literally the string "null", remove the value's key.
// This allows helm's multiple value sources (value files or --set) to
// remove incompatible keys from the previous values.
if v == "null" {
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -273,6 +273,14 @@ func (i *installCmd) run() error {
// Merges source and destination map, preferring values from the source map
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
for k, v := range src {
// If the new value is literally the string "null", remove the value's key.
// This allows helm's multiple value sources (value files or --set) to
Copy link
Contributor

Choose a reason for hiding this comment

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

  • s/helm's/Helm's/
  • s/multiple value sources/various sources of values/

@scottrigby
Copy link
Member Author

@thomastaylor312 @seh I just updated this to cover earlier feedback.

Thanks @rabellamy for pairing on Golang syntax.

@seh
Copy link
Contributor

seh commented Jul 6, 2017

I'm trusting @thomastaylor312 that the accommodation is in the right place now.


Reviewed 2 of 2 files at r2.
Review status: all files reviewed at latest revision, 2 unresolved discussions.


pkg/chartutil/values.go, line 281 at r2 (raw file):

is YAML null

This reads strangely. How about "is the YAML null value?"


pkg/chartutil/values.go, line 286 at r2 (raw file):

		// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362
		switch val {
		case "null":

You can instead write the following:

case "null", "Null", "NULL", "~":

Alternately, use a regular expression.


Comments from Reviewable

@seh
Copy link
Contributor

seh commented Jul 6, 2017

:lgtm:


Reviewed 1 of 1 files at r3.
Review status: all files reviewed at latest revision, 1 unresolved discussion.


pkg/chartutil/values.go, line 286 at r2 (raw file):

Previously, seh (Steven E. Harris) wrote…

You can instead write the following:

case "null", "Null", "NULL", "~":

Alternately, use a regular expression.

I like the regular expression approach because it allows you to write the following:

if stringIsYAMLNull(val) {
  delete(v, key)
  continue
}

The single-case switch looks a little weird to me, but I concede that the regular expression version also either requires or at least could involve a separate line to compile the regular expression pattern. If you don't want to save the compiled pattern, you can use regexp.Match or regexp.MatchString.


Comments from Reviewable

@scottrigby
Copy link
Member Author

@seh I'd be OK with adding the regex check. Honestly that does feel cleaner (and more reusable) to me too. I'll ping you on Slack to make sure I'm 100% clear on how and where you'd like organized in the codebase.

@scottrigby
Copy link
Member Author

@seh I changed the switch case to a simple in-place version of regexp.MatchString rather than adding a new complied stringIsYAMLNull. Mainly because I'm not sure whether this has any other use value besides here. Either way, I'd love more feedback on this as this as-is… and then if compiling is preferable I can convert to that.

@scottrigby
Copy link
Member Author

@thomastaylor312 OK, I changed to nil comparison. Thanks for checking that this is the case 💯

If this needs a test, can you point me in the right direction of where to look for the existing tests here? Also, could a test be a follow-up PR, or should it be the same one?

@scottrigby
Copy link
Member Author

@thomastaylor312 How do these tests look?

@@ -316,6 +322,8 @@ func TestCoalesceValues(t *testing.T) {
expect string
}{
{"{{.top}}", "yup"},
// The keys bottom, right, left, front, and back should not be present.
{"{{.side}}", ""},
Copy link
Contributor

Choose a reason for hiding this comment

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

These look great, we do need to test that all of the other keys are unset. This was a learning experience for me too, but I found out it should look like this: {"{{.bottom}}", "<no value>"},

Copy link
Member Author

Choose a reason for hiding this comment

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

I saw that, but doesn't that signify the key is present but the value is empty? Is there currently a test method available that checks if a key is not present?

Copy link
Contributor

Choose a reason for hiding this comment

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

Definitely a good point. We are talking about this now in Slack to get some ideas

@thomastaylor312
Copy link
Contributor

I manually tested this and it is good to go as soon as we have those last unit test cases. Thanks for all the hard work @scottrigby!

@scottrigby
Copy link
Member Author

@thomastaylor312 OK I updated the PR with the check to ensure keys are not present in the Values map. I just defined an array of the keys that should have null value properly recognized (and converted to nil by the yaml parser), and looped over them to check that they are not in the map.

Also, thanks for helping me find what type Values is (from /pkg/chartutil/values.go: type Values map[string]interface{}. From the documentation of CoalesceValues it was not clear to me that Values referred to a type declaration).

@scottrigby scottrigby force-pushed the allow-delete-key branch 2 times, most recently from 7a09ebc to 2918e0c Compare July 7, 2017 17:42
@scottrigby
Copy link
Member Author

Note that the documentation is the only clear example I can think of now, because it's from a PR I have worked on helm/charts#1375, but that PR is not yet merged. However, it clearly explains the functionality, and I tried to make it match the rhetorical style of the rest of the doc (except I added a header for clarity). If there is another, better example, we can use that. But it does feel like an example helps. Thoughts?

@thomastaylor312
Copy link
Contributor

The docs look good to me. Because circle isn't running the unit tests, I am running those locally and validating that they pass

…is nil.

- Note that this covers all YAML null syntax options:
  ref: http://yaml.org/type/null.html
- Note that we do a nil comparison because the encoding/yaml package parses
  YAML properly and any variation of null, Null, NULL, or ~ is converted to nil
  by the time we get here.
@scottrigby
Copy link
Member Author

scottrigby commented Jul 7, 2017

Rebased against kubernetes/master now.

@thomastaylor312
Copy link
Contributor

Ok, I checked that the tests are passing, so this should be good to go. Thanks for the contribution @scottrigby!

@thomastaylor312 thomastaylor312 dismissed seh’s stale review July 7, 2017 18:45

He LGTM'd it in Reviewable

@thomastaylor312 thomastaylor312 merged commit 97818b1 into helm:master Jul 7, 2017
Copy link
Contributor

@seh seh left a comment

Choose a reason for hiding this comment

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

I missed the merge here, but thought it was still worth noting these suggestions.

@@ -281,6 +281,13 @@ func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interf
if _, ok := v[key]; !ok {
// If the key is not in v, copy it from nv.
v[key] = val
} else if ok && v[key] == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Here, we could have avoided a second map lookup by binding the mapped value to a variable at line 281 above:
if value, ok := v[key]; ! ok {
  // ...
} else if ok && value == nil {
  // When the YAML ...
}

I'd consider then turning the conditional around:

if value, ok := v[key]; ok {
  if value == nil {
    // When the YAML ...
  } else if dest, ok := value.(map[string]interface{}); ok {
    // If v[key] is a table ...
  }
} else {
  // If the key is not in v ...
}

Copy link
Contributor

Choose a reason for hiding this comment

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

@seh If you create an issue and assign it to me, I can make these small changes

Copy link
Contributor

Choose a reason for hiding this comment

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

That became #2663.

// When the YAML value is null, we remove the value's key.
// This allows Helm's various sources of values (value files or --set) to
// remove incompatible keys from any previous chart, file, or set values.
// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362
Copy link
Contributor

Choose a reason for hiding this comment

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

  • I'd prefer to move this reference over into the pertinent test function in file values_test.go (around line 280 there).

@seh
Copy link
Contributor

seh commented Jul 7, 2017

By the way, the go-yaml/yaml package uses a map of strings to resolve special identifiers to YAML tags. Here we find the set that maps to the "null" tag. Note that the empty string is one of the keys, per the specification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. feature in progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants