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

[helm-operator] Fix reconciliation of resources with jsonpatch #2777

Merged
merged 1 commit into from
Apr 7, 2020

Conversation

Lucaber
Copy link
Contributor

@Lucaber Lucaber commented Apr 3, 2020

This PR updates the jsonpatch library to fix an issue during reconciliation of arrays.

The old version had an incomplete implementation for json patches of arrays.
A diff between

containers:
    - name: container1
    - name: container2

and

containers:
    - name: container1

created 3 patch operations:

  • remove containers/0
  • remove containers/1
  • add containers/0

This causes an issue because we are ignoring all "remove" operations to allow manual modifications.

Helm tries to apply

containers:
    - name: container1
    - name: container2
    - name: container1
invalid: spec.template.spec.containers[0].name: Duplicate value:
"container1"

The new jsonpatch version does only return a remove containers/1
operation (which we are still ignoring because it could be a manual
change, but does not fail)

@joelanford
Copy link
Member

@Lucaber Thanks for reporting this and contributing a fix! This definitely looks like an area ripe for bugs, but I haven't been able to reproduce it. I'm probably not fully understanding the scenario correctly. What is the array that helm is managing via the CR and its release, and what is the manual change?

I'm also wondering if we could make use of k8s.io/apimachinery/pkg/util/strategicpatch.CreateThreeWayPatch, since that's basically what we're trying to accomplish it seems like: "I applied A, it was changed to B and I want to re-apply A"

@joelanford
Copy link
Member

Scratch that, I reproduced by creating an operator from the default nginx helm chart, running it and deploying the default CR, and then manually editing the Deployment to add an extra container.

Looks like your patch fixes it! If you're interested in researching the strategic merge patch option, I can leave this open. Otherwise, I'm good with this approach and researching the strategic merge patch option later.

Copy link
Contributor

@camilamacedo86 camilamacedo86 left a comment

Choose a reason for hiding this comment

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

It shows fine for me. But, could we add a test for this scenario as well?

@Lucaber
Copy link
Contributor Author

Lucaber commented Apr 6, 2020

It shows fine for me. But, could we add a test for this scenario as well?
Otherwise, /lgtm /approved

I added some tests for the generatePatch function.

I'm also wondering if we could make use of k8s.io/apimachinery/pkg/util/strategicpatch.CreateThreeWayPatch, since that's basically what we're trying to accomplish it seems like: "I applied A, it was changed to B and I want to re-apply A"

This sounds perfect, but i'm not familiar with the k8s apis at all. I will take a look at this later. Maybe we could merge this fix first?

We ran into this issue because we are using the jaeger-operator which injects a tracing sidecar container into every deployment.

@Lucaber Lucaber force-pushed the fix/jsonpatch branch 3 times, most recently from 88b804f to 0cb2481 Compare April 6, 2020 09:27
CHANGELOG.md Outdated Show resolved Hide resolved
if op.Operation != "remove" {
if op.Operation == "remove" {
removeOps = append(removeOps, op)
} else if !(op.Operation == "add" && op.Value == nil) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should not be just else?
What happens if it faces a conditional that is not defined here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had issues with chart templates that contain fields with a value of null. During every reconciliation, the operator tried to apply these changes to Kubernetes, but Kubernetes ignores null values completely and they do not appear in for example kubectl get -o yaml. So one minute later, the operator tries it again and again..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add a test case for this

}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

It was added after :

// If there are no patch operations, return nil. Callers are expected
	// to check for a nil response and skip the patch operation to avoid
	// unnecessary chatter with the API server.
	if len(patchOps) == 0 {
		return nil, nil
	}

Should not be before?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the patchOps slice is empty, the next for-loop wont change it anyway, so we can already return here

}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, see that the above is not covered by the tests. See: https://coveralls.io/builds/29873112/source?filename=pkg/helm/release/manager.go#L304

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added a new test case to cover this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well no, i didn't, i cant replicate this issue with the new jsonpath library, should i remove this?

Copy link
Member

Choose a reason for hiding this comment

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

Am I understanding correctly that it seems like the new library correctly chooses replace rather than adding both an add and remove?

Does that just mean this PR becomes just the dependency change to use the new library?

Copy link
Member

Choose a reason for hiding this comment

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

And the added unit tests 🙏 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, i removed the additional for-loop. I think this issue is fixed in the new jsonpatch library

Copy link
Contributor

@camilamacedo86 camilamacedo86 left a comment

Choose a reason for hiding this comment

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

Just a few questions and nits.

@Lucaber Lucaber force-pushed the fix/jsonpatch branch 4 times, most recently from 6cc7647 to 40afb0f Compare April 6, 2020 15:28
Copy link
Contributor

@camilamacedo86 camilamacedo86 left a comment

Choose a reason for hiding this comment

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

HI @Lucaber,

Needs fix the lint issue:

Running golangci-lint
pkg/helm/release/manager.go:283:22: SA4010: this result of append is never used, except maybe in other appends (staticcheck)
			removeOps = append(removeOps, op)
			                  ^

To test it locally you can run make lint

This PR updates the jsonpatch library to fix an issue during reconcilation of arrays.

The old version had an incomplete implementation for json patches of arrays.
A diff between
```
containers:
    - name: container1
    - name: container2
```
and
```
containers:
    - name: container1
```

created 3 patch operations:
- remove containers/0
- remove containers/1
- add containers/0

This causes an issue because we are ignoring all "remove" operations to allow manual modifications.

Helm tries to apply
```
containers:
    - name: container1
    - name: container2
    - name: container1
```

```
invalid: spec.template.spec.containers[0].name: Duplicate value:
"container1"
```

The new jsonpatch version does only return a `remove containers/1`
operation (which we are still ignoring because it could be a manual
change, but does not fail)
patchOps := make([]jsonpatch.JsonPatchOperation, 0)
for _, op := range ops {
if op.Operation != "remove" {
if op.Operation != "remove" && !(op.Operation == "add" && op.Value == nil) {
Copy link
Contributor

@camilamacedo86 camilamacedo86 Apr 6, 2020

Choose a reason for hiding this comment

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

👍 now it shows ok

Copy link
Member

@joelanford joelanford left a comment

Choose a reason for hiding this comment

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

/lgtm

Thanks for the fix and the quick turnaround on the reviews!

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Apr 7, 2020
Copy link
Contributor

@camilamacedo86 camilamacedo86 left a comment

Choose a reason for hiding this comment

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

/lgtm
/approved

Terrific job 👍

@camilamacedo86 camilamacedo86 merged commit b88da52 into operator-framework:master Apr 7, 2020
camilamacedo86 pushed a commit that referenced this pull request Apr 21, 2020
…ative k8s objects (#2869)

A follow-up PR to #2808 and #2777. @joelanford

In this PR we are using the Kubernetes strategic three-way merge patch for patches by the helm-operator for native Kubernetes objects.
This ensures that we are using the correct merge strategy that is defined in the OpenAPI schema of the object. #2808

However, we do not have the required schema information for custom resources(CRs). So we need to fall back to the previous manual json-patch.
To detect CRs we are using the same method as helm https://github.com/helm/helm/blob/b21b00978539ea8270e6b672bc1e6bc07af61475/pkg/kube/client.go#L391

Co-authored-by: Joe Lanford <joe.lanford@gmail.com>
Co-authored-by: Henning Surmeier <h.surmeier@mittwald.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lgtm Indicates that a PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants