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

Load chart and provide access in both v2 and v3 structs. #1362

Merged
merged 6 commits into from
Dec 13, 2019

Conversation

absoludity
Copy link
Contributor

Description of the change

In #1309 (comment) , related to the helm3 work, @latiif mentioned he was thinking about a function to convert between v2 and v3 chart structures. This PR is an alternative that relies on the respective v2/v3 loaders to ensure we always have access to the chart in both formats when working with Kubeapps' pkg/chart.

cc @SimonAlling

Benefits

I think this should be safer since we only require read-access to the chart structs (? I assume), so there should be no consistency issues, and we're relying on the actual helm loaders rather than our own conversion function.

It should still provide the benefits outlined by @latiif (re-use of existing code for repo management and syncing).

See what you think :)

Possible drawbacks

Applicable issues

Ref #1056

Additional information

@absoludity absoludity self-assigned this Dec 11, 2019
Copy link
Contributor Author

@absoludity absoludity left a comment

Choose a reason for hiding this comment

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

There were a couple of other drive-by improvements which I included, see inline comments.

// Chart struct contains the clients required to retrieve charts info
type Chart struct {
// ChartClient struct contains the clients required to retrieve charts info
type ChartClient struct {
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 think I've mentioned this before, but I found it confusing that we had a struct called Chart which wasn't a chart, but a struct containing clients for getting chart details. Hence the rename.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's way less ambiguous

kubeClient kubernetes.Interface
appRepoClient appRepoClientSet.Interface
load LoadChart
Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAICT, the LoadChart function was only injected into the struct so that tests could use a fake? At least, that was the only place a different LoadChart was used... the loader was hard-coded in the main.go. I've instead updated the tests so that they don't need a fake, but instead load a real chart from test_data (I took the current bitnami/nginx).

I initially packaged the nginx chart using both helm2 and 3, and although you end up with a different tarball, tests pass with both, so I've just left the helm2 packaged one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it was only for testing purposes. What I don't like about using real data is that you cannot modify the input (so you are only testing the happy path). Since here we are only testing the happy path anyway I am okay with the change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is possible to both use real data and modify the input - I've just not done so here because it wasn't necessary for my simple test. But if we want to in the future, we can extend this with something like the helm tests for the archive loader, in https://github.com/helm/helm/blob/master/pkg/chart/loader/archive_test.go where you generate the archive with the setup for your test.

Values: &chart.Config{
Raw: details.Values,
func (f *FakeChart) GetChart(details *chartUtils.Details, netClient chartUtils.HTTPClient) (*chartUtils.ChartMultiVersion, error) {
return &chartUtils.ChartMultiVersion{
Copy link
Contributor

Choose a reason for hiding this comment

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

Should there be no V3 in this ChartMultiVersion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently the FakeChart is only used in a test which is testing the HelmV2 charts, so I didn't add dead code. We can add a V3 if/when the FakeChart[Client] is used with HelmV3 charts.

@SimonAlling
Copy link
Contributor

That's a great job you've done there, @absoludity!

Comment on lines 251 to 255
chartV2, err := helm2loader.LoadArchive(bytes.NewReader(data))
if err != nil {
return nil, err
}
chartV3, err := helm3loader.LoadArchive(bytes.NewReader(data))
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 probably do something like lol := bytes.NewReader(data) and then use lol twice (assuming it typechecks).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does look like it should be DRYer, but bytes.NewReader() returns a Reader interface, which doesn't have a seek. So it can only be read once. I'd have to convert it to a SectionReader (so I can call Seek(0) after the first LoadArchive), but it ends up more complicated than this. Try it out (and let me know if you find a better option :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Hehe, I suspected there could be some black magic like that going on. I'm absolutely fine with the current implementation. 😄

Copy link
Contributor

@andresmgot andresmgot left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! I have a comment I would like to get your thoughts about:

kubeClient kubernetes.Interface
appRepoClient appRepoClientSet.Interface
load LoadChart
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, it was only for testing purposes. What I don't like about using real data is that you cannot modify the input (so you are only testing the happy path). Since here we are only testing the happy path anyway I am okay with the change.

Comment on lines 251 to 259
chartV2, err := helm2loader.LoadArchive(bytes.NewReader(data))
if err != nil {
return nil, err
}
chartV3, err := helm3loader.LoadArchive(bytes.NewReader(data))
if err != nil {
return nil, err
}
return &ChartMultiVersion{V2: chartV2, V3: chartV3}, nil
Copy link
Contributor

@andresmgot andresmgot Dec 11, 2019

Choose a reason for hiding this comment

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

I would assume this will bite us in the future. There may be cases in the future that a chart is only compatible with the v3 format. Trying to load as a v2 will likely fail. To avoid returning at that point, we can store the errors in the ChartMultiVersion struct so the caller can decide if the chart version that was expecting (if any) failed.

Also, another thing is that the naming can be a bit confusing (even though I think there is not much we can do). The chart APIVersion used in Helm v2 is v1 while the new version introduced in Helm 3 is v2. Because of that, a chartV2 may mean a chart for Helm v2 or a chart with an APIVersion v2 (used only by Helm 3).

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, we'll definitely need to be able to handle Helm 3-only charts. Would it suffice to have V2 be nil in that case, in the (apparent) absence of type constructors like Maybe and Either?

Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the naming: Yes, that is a problem. One solution could be to use names like chartHelm2 instead of chartv2. Another solution, obviously, is to keep the current naming but with v1 and v2 instead of v2 and v3.

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 would assume this will bite us in the future. There may be cases in the future that a chart is only compatible with the v3 format. Trying to load as a v2 will likely fail. To avoid returning at that point, we can store the errors in the ChartMultiVersion struct so the caller can decide if the chart version that was expecting (if any) failed.

Yeah, I'm assuming this is transition-only code. Right now they load for both. Later, if/when backwards incompatible changes are added to the chart format, then helm will be at a point where it only supports the new version, so we can update to do the same - I assume?

Also, another thing is that the naming can be a bit confusing (even though I think there is not much we can do). The chart APIVersion used in Helm v2 is v1 while the new version introduced in Helm 3 is v2. Because of that, a chartV2 may mean a chart for Helm v2 or a chart with an APIVersion v2 (used only by Helm 3).

Ah - I assumed in this test in helm https://github.com/helm/helm/blob/master/pkg/chart/loader/load_test.go#L101 that the V1 was an older format, but from what you're saying it's the format used with helm v2? What do you recommend naming-wise? I could use either V1 and V2, or use HelmV2 and HelmV3 to be explicit? Let me know.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, we'll definitely need to be able to handle Helm 3-only charts.

Yep, but that's in the future, right? (I know there are currently different hooks supported, but I didn't think there was a different format right now, as in a chart which wouldn't load in helm2 or the other way around).

Would it suffice to have V2 be nil in that case, in the (apparent) absence of type constructors like Maybe and Either?

Yes, in the future, it'll be no problem for V2 to be nil if it doesn't load (once we've also transition the repo sync code etc. to use the v3 one instead). But I'd think at that point we'd just be removing it.

Regarding the naming: Yes, that is a problem. One solution could be to use names like chartHelm2 instead of chartv2. Another solution, obviously, is to keep the current naming but with v1 and v2 instead of v2 and v3.

Yep, so what we're referring to is the struct for a chart in helm2 or the struct for a chart in helm3, so I've updated to use helm2chart and helm3chart respectively. See what you think.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, but that's in the future, right?

Shouldn't we support v2 of the chart API (i.e. Helm 3) as soon as we claim to have "Helm 3 support"? Such charts are not supported by Helm 2 AFAIK.

Copy link
Contributor

@andresmgot andresmgot Dec 11, 2019

Choose a reason for hiding this comment

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

I would still do that now. Mostly because this will hit us for sure (and better for us to fix it in advance) and also because we may not notice and users could find it instead. It's also an easy fix.

Regarding the naming, it's less important. To be explicit we can say chartHelm2 and chartHelm3 but up to you if you want to leave it as it is. Note that chartHelm3 (at least from what I can see in the code`) has support for charts v1 and v2.

Copy link
Contributor Author

@absoludity absoludity Dec 12, 2019

Choose a reason for hiding this comment

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

Yep, but that's in the future, right?

Shouldn't we support v2 of the chart API (i.e. Helm 3) as soon as we claim to have "Helm 3 support"? Such charts are not supported by Helm 2 AFAIK.

Yes, we definitely need to support the v2 charts as soon as we claim to have "Helm3 support", and you're right: if I update the apiVersion of my test nginx chart to v2 I can trigger an error during my TestGetChart test, because the helm2 (v1) loader refuses to load a chart with apiVersion: v2. (I had previously only packaged the chart with helm3 without updating the apiVersion in the Chart.yaml). I've updated and added tests showing this (more below)

But I don't think we can support v2 charts in one hit here, we need to (a) unblock the helm3 work, and (b) plan steps towards supporting both v1 and v2 charts. There are currently no v2 charts in stable, incubator or bitnami (but yes, it's only a matter of time).

I would still do that now. Mostly because this will hit us for sure (and better for us to fix it in advance) and also because we may not notice and users could find it instead. It's also an easy fix.

I've updated to handle the case (see the extra tests). Rather than storing the error, I've added a flag requireV1Support to GetChart so that the caller can choose to require and handle errors (ie. tiller proxy), or not require v1 support (and not have an error to handle - this will be what the kubeops will use eventually, once we've removed the dependence on the v1 chart for other operations in a later branch).

This should unblock the helm3 work for now where v1 support is not required (though can still be used so that existing versions of proxy.ResolveManifest etc. functions can used until their helm3 versions are created bit-by-bit). Note: it's not clear to me we've also lost the original intention of @latiif's question: I don't see how we can the chart management/syncing code is relevant here, but assume you meant the existing code in the proxy library? Let me know.

Regarding the naming, it's less important. To be explicit we can say chartHelm2 and chartHelm3 but up to you if you want to leave it as it is. Note that chartHelm3 (at least from what I can see in the code`) has support for charts v1 and v2.

Yep, I'd already updated to helm2chart and helm3chart respectively.

Thanks!

Copy link
Contributor Author

@absoludity absoludity left a comment

Choose a reason for hiding this comment

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

Thanks for the input people (comments inline). @latiif does this fix/unblock you though? I think it does based on what you said, but want to be sure before I land once reviewed.

kubeClient kubernetes.Interface
appRepoClient appRepoClientSet.Interface
load LoadChart
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is possible to both use real data and modify the input - I've just not done so here because it wasn't necessary for my simple test. But if we want to in the future, we can extend this with something like the helm tests for the archive loader, in https://github.com/helm/helm/blob/master/pkg/chart/loader/archive_test.go where you generate the archive with the setup for your test.

Comment on lines 251 to 255
chartV2, err := helm2loader.LoadArchive(bytes.NewReader(data))
if err != nil {
return nil, err
}
chartV3, err := helm3loader.LoadArchive(bytes.NewReader(data))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does look like it should be DRYer, but bytes.NewReader() returns a Reader interface, which doesn't have a seek. So it can only be read once. I'd have to convert it to a SectionReader (so I can call Seek(0) after the first LoadArchive), but it ends up more complicated than this. Try it out (and let me know if you find a better option :)

Comment on lines 251 to 259
chartV2, err := helm2loader.LoadArchive(bytes.NewReader(data))
if err != nil {
return nil, err
}
chartV3, err := helm3loader.LoadArchive(bytes.NewReader(data))
if err != nil {
return nil, err
}
return &ChartMultiVersion{V2: chartV2, V3: chartV3}, nil
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 would assume this will bite us in the future. There may be cases in the future that a chart is only compatible with the v3 format. Trying to load as a v2 will likely fail. To avoid returning at that point, we can store the errors in the ChartMultiVersion struct so the caller can decide if the chart version that was expecting (if any) failed.

Yeah, I'm assuming this is transition-only code. Right now they load for both. Later, if/when backwards incompatible changes are added to the chart format, then helm will be at a point where it only supports the new version, so we can update to do the same - I assume?

Also, another thing is that the naming can be a bit confusing (even though I think there is not much we can do). The chart APIVersion used in Helm v2 is v1 while the new version introduced in Helm 3 is v2. Because of that, a chartV2 may mean a chart for Helm v2 or a chart with an APIVersion v2 (used only by Helm 3).

Ah - I assumed in this test in helm https://github.com/helm/helm/blob/master/pkg/chart/loader/load_test.go#L101 that the V1 was an older format, but from what you're saying it's the format used with helm v2? What do you recommend naming-wise? I could use either V1 and V2, or use HelmV2 and HelmV3 to be explicit? Let me know.

Values: &chart.Config{
Raw: details.Values,
func (f *FakeChart) GetChart(details *chartUtils.Details, netClient chartUtils.HTTPClient) (*chartUtils.ChartMultiVersion, error) {
return &chartUtils.ChartMultiVersion{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently the FakeChart is only used in a test which is testing the HelmV2 charts, so I didn't add dead code. We can add a V3 if/when the FakeChart[Client] is used with HelmV3 charts.

@SimonAlling
Copy link
Contributor

@latiif does this fix/unblock you though?

Should. But hard to say for sure before we have tried building on top of your code, which we'll do ASAP.

Copy link
Contributor

@andresmgot andresmgot left a comment

Choose a reason for hiding this comment

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

Giving my +1. Thanks!

@absoludity absoludity merged commit b3e0d07 into vmware-tanzu:master Dec 13, 2019
@absoludity absoludity deleted the chart-v2-v3 branch December 13, 2019 00:23
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.

4 participants