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
add multi-part yaml support to GetChanges for the bundle facade #13448
add multi-part yaml support to GetChanges for the bundle facade #13448
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code as-is should work fine for the majority of bundles. However, there are some (non-trivial) edge cases that you need to address before landing this:
- Juju deploy allows you to specify a base bundle (+optional overlay(s) if you use the multi-doc syntax) plus any number of explicitly defined overlay files (e.g.
juju deploy ./multidoc.yaml --overlay foo.yaml --overlay bar.yaml
). You can technically concatenate them client-side before calling the facade (just add them as extra docs after the base). However, (there is a tricky bit here: - Bundles support an "include" directive (search for
include-file
here). The include path is relative to the overlay location which is why you see this mechanism with multiple bundle data sources. Since the facade logic cannot report back errors like "give me file X" you will need to emulate this logic and expand include-{file,base64} directives client-side where you can access the files. - Bundles/overlays can reference local charms (again, paths are relative to the thing that references them). I am not 100% sure how this can be made to work easily with the python client.
For point 2, you can take a look at the QA steps here to get a better idea about how it is supposed to work.
Note however that when the server-side bundle deployment get implemented, this will all become much easier for both the cli and pylibjuju as they just need to implement a simple challenge/response protocol and the controller will do all the grunt work.
Changes, along with their passing tests are posted on juju/python-libjuju#566 for all the three points:
See the test named
See the tests named
Turns out this is already working in pylibjuju, see this test, where the applications:
test1:
charm: "../charm"
num_units: 1
test2:
charm: "../charm.charm"
num_units: 1 So as you suggested, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to bump the API. Current implementations might break if they see a multipart yaml doc and we don't want to do that. So the change is good if we add a new API revision.
I don't think I follow, the only change this has is for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there. The facades are bumped correctly, we just need to wire up the different function calls depending on the version of the API.
@@ -219,7 +237,8 @@ type validators struct { | |||
func getBundleChanges(args params.BundleChangesParams, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So for the v1-v5 of the api, we want to call the old getBundleChanges
. For the v6 of the api we want to call this new method.
@@ -213,6 +213,109 @@ func (s *bundleSuite) TestGetChangesSuccessV2(c *gc.C) { | |||
c.Assert(r.Errors, gc.IsNil) | |||
} | |||
|
|||
func (s *bundleSuite) TestGetChangesSuccessV2WithOverlays(c *gc.C) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Please rename to TestGetChangesSuccessWithOverlays. V2 is confusing as V5 fails with overlays.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure why I named it V2 tbh, must be a copy paste overlook, good catch!
`, | ||
} | ||
api := s.makeAPI(c) | ||
apiv1 := &bundle.APIv1{&bundle.APIv2{&bundle.APIv3{&bundle.APIv4{&bundle.APIv5{api}}}}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test fails when: apiv5 := &bundle.APIv5{api}
. Looks like we're calling the wrong getChanges(). Will need a test for v1, v5 and the most recent version.
r, err := apiv1.GetChanges(args) | ||
c.Assert(err, jc.ErrorIsNil) | ||
// This is the inverted test of TestGetChangesSuccessV2WithOverlays | ||
c.Assert(r.Changes, gc.Not(jc.DeepEquals), []*params.BundleChange{{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make this an explicit check for content. Not here could be almost empty.
@hmlanigan thanks for pointing out the issues! Hope I was able to make things a bit clearer:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm approving to unlock the request changes blocker.
I haven't Q&A'd this yet though.
@@ -166,7 +183,7 @@ func NewBundleAPIv1( | |||
if err != nil { | |||
return nil, errors.Trace(err) | |||
} | |||
return &APIv1{&APIv2{&APIv3{&APIv4{&APIv5{api}}}}}, nil | |||
return &APIv1{&APIv2{&APIv3{&APIv4{&APIv5{&APIv6{api}}}}}}, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like fun...
return err | ||
}, | ||
} | ||
return b.getChanges(args, vs, func(changes []bundlechanges.Change, results *params.BundleChangesResults) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having to duplicate all this code makes it really hard to track what calls what.
How about we change the b.getChanges
signature to include a boolean flag (or alternatively, a typed enum for more clarity) which indicates the parse mode (e.g. includeOverlays
) to use?
Then you can extract the common GetChanges
logic into a private method which accepts this flag and just provide two tiny stubs for APIV5 and BundleAPI which pass the right value through.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, the duplication adds more indirection on the control flow. I initially tried to duplicate only the b.getChanges
but it didn't work for reasons related to Go semantics that I don't understand yet.
However, if I'm understanding your proposal correctly, I'm also doubtful that adding another mechanic to separate functionality between versions would make this less confusing. Especially when more version breaks are added in the future, this might potentially turn into (already existing) duplicate codes with big switches in some of the functions.
Also, code duplication is often really bad for like maintainance/debug etc, but this duplication is like grabbing a snapshot of the current version and moving on, so day 2 ops are still gonna be done on the func (b *BundleAPI) GetChanges
(unless it's specifically about <=APIv5
). On the other hand, adding anohter argument would sort of cram all the logics into one spot, thereby forcing anyone maintaining/improving it in the future to understand all of it and make sure they're not breaking any other version logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion was something along these lines:
func (b *APIv5) GetChanges(args params.BundleChangesParams) (params.BundleChangesResults, error) {
return b.doGetChanges(args, false)
}
// Versions 6+
func (b *BundleAPI) GetChanges(args params.BundleChangesParams) (params.BundleChangesResults, error) {
return b.doGetChanges(args, true)
}
// You could also pass validators as an arg and use the same method for pre V5 facades
func (b *BundleAPI) doGetChanges(args params.BundleChangesParams, expandOverlays bool) (params.BundleChangesResults, error) {
vs := validators{
verifyConstraints: func(s string) error {
_, err := constraints.Parse(s)
return err
},
verifyStorage: func(s string) error {
_, err := storage.ParseConstraints(s)
return err
},
verifyDevices: func(s string) error {
_, err := devices.ParseConstraints(s)
return err
},
}
return b.getBundleChanges(args, vs, expandOverlays, mapBundleChanges)
}
func mapBundleChanges(changes []bundlechanges.Change, results *params.BundleChangesResults) error {
results.Changes = make([]*params.BundleChange, len(changes))
for i, c := range changes {
var guiArgs []interface{}
switch c := c.(type) {
case *bundlechanges.AddApplicationChange:
guiArgs = c.GUIArgsWithDevices()
default:
guiArgs = c.GUIArgs()
}
results.Changes[i] = ¶ms.BundleChange{
Id: c.Id(),
Method: c.Method(),
Args: guiArgs,
Requires: c.Requires(),
}
}
return nil
}
func (b *BundleAPI) getBundleChanges(...){
// merge getChanges + getBundleChanges logic
}
The way that the facade abuses inheritance should allow this to work but as you can see the code is much more compact. Also, note that when we move to server-side bundle expansion this facade will become obsolete as we will be using the same code path for both the juju cli and pylibjuju and the controller will take care of calculating and applying the required changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@achilleasa Ah ok I see what you're saying, I agree, this looks much better, I'll see what I can do, thanks for the input 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to add this handy snippet if you want to add some more context instead of passing true/false:
const (
ignoreOverlays = false
includeOverlays = true
)
...
func (b *APIv5) GetChanges(args params.BundleChangesParams) (params.BundleChangesResults, error) {
return b.doGetChanges(args, ignoreOverlays)
}
// Versions 6+
func (b *BundleAPI) GetChanges(args params.BundleChangesParams) (params.BundleChangesResults, error) {
return b.doGetChanges(args, includeOverlays)
}
@@ -196,7 +244,8 @@ func (b *APIv1) GetChanges(args params.BundleChangesParams) (params.BundleChange | |||
}, | |||
verifyDevices: nil, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line can be omitted as the default zero value is nil
.
@@ -196,7 +244,8 @@ func (b *APIv1) GetChanges(args params.BundleChangesParams) (params.BundleChange | |||
}, | |||
verifyDevices: nil, | |||
} | |||
return getChanges(args, vs, func(changes []bundlechanges.Change, results *params.BundleChangesResults) error { | |||
|
|||
postProcess := func(changes []bundlechanges.Change, results *params.BundleChangesResults) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend extracting this, and the v5 below (L270) to standalone functions to improve readability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for applying the extra changes to clean up the code. Happy to see this landed if CI is green.
|
JUJU-142 |
#566 ### Description This PR adds the support for overlays in bundle deployments. Fixes #510 This PR relies on a change in Juju's api for getting changes for bundles with overlays (multi-part yaml support), juju/juju#13448. Jira card [#142](https://warthogs.atlassian.net/browse/JUJU-142) ### QA Steps `tests/integration/test_model.py` includes some new tests for the added support. ``` tox -e integration -- tests/integration/test_model.py ``` ### Notes & Discussion Please do not merge yet, as a couple of small things need to be done/added for this to be ready to land: - [x] Add a PR on Juju for `GetChange` juju/juju#13448 - [x] Land that PR on Juju - [x] Charmstore bundles with `--overlay` argument, along with its test - [x] A test for a multi-part overlay as an `--overlay` argument to a local bundle being deployed - [x] A test for a multi-part overlay as an `--overlay` argument to a charmstore bundle being deployed - [x] Make sure that we resolve and inline `config: include-file://` and `config: include-base64://` here in `pylibjuju` side
#13528 Merge 2.9 #13461 Enhanced logging detail when starting side-car agents #13462 Context queue #13464 Improve run hook error when charm is missing #13468 Skip check for microk8s user group on macOS #13472 Update to latest version of Pebble (exec terminal/interactive params) #13448 add multi-part yaml support to GetChanges for the bundle facade #13465 [JUJU-112] manifests and blobs api for registries #13460 Allow NotifyTarget to return an error #13482 [JUJU-136] enable juju_machine_lock to display unit machine lock status. #13477 Added lower bound for run_action wait timeout. #13490 [JUJU-121] Adds support for auto create of instance profiles. #13425 [2.9] Expose supported features in show model output #13426 Remove deprecated juju cli flags #13422 Provision OpenStack instances with NICs for bindings #13423 Fix add-machine command help for zones constraint #13424 Fixed a typo in README.md #13513 [JUJU-223] Fix panic when retrieving application config #13517 [JUJU-227] Stop the CAAS storage provisioner worker if the application is dead; #13519 [JUJU-240] Use the storage prefix when selecting pvcs for sidecar charms #13520 [JUJU-245] Do not error with permission denied if a controller tries to access removed storage #13523 Logging for link-layer device updates #13501 [JUJU-197] Raft lease retries with exceeded dropped #13514 [JUJU-230] Add additional unit test for WatchAllModels api #13512 [JUJU-208] Lease Diff #13515 Remove unused ExportModel method #13516 [JUJU-235] Remove bogus attachment dead warnings #13518 [JUJU-237] Fix the failure to update homebrew on none juju/juju #13498 [JUJU-113] Refactor upgrade controller command #13502 [JUJU-199] Remove an an old legacy cmr fallback and add upgrade step #13506 [JUJU-207] Apply synchronously, respond asynchronously #13508 [JUJU-176] Fix mongo service enable by using start not restart. #13510 [JUJU-221] Fix vol attachment life asserts for force remove #13511 [JUJU-215] Ignore notfound error from fetching pod status for updating unit status; #13500 [JUJU-192] Use yaml.safe_load instead of yaml.load #13503 [JUJU-202] Lease manager test fix and trace logging #13504 [JUJU-195] Format logs for mongo version more nicely #13505 [JUJU-193] [2.9] Populate supported assumes features for k8s provider #13480 Added Jammy series as supported. #13488 [JUJU-117] Don't bake defaults when updating config. #13492 [JUJU-180] Correct affected unit reporting in upgrade-series #13495 [JUJU-186] Raft client logging levels #13496 [JUJU-187] Improve the help text for --keep-broken flag #13493 [JUJU-185] Use the correct ingress resource version for the k8s cluster #13491 [JUJU-178] Preserve old semantics for run-action concerning wait argument #13489 [JUJU-177] Check for available disk space before doing a juju backup ``` # Conflicts: # apiserver/facades/agent/provisioner/provisioninginfo_test.go # apiserver/facades/client/application/application.go # apiserver/facades/client/application/application_unit_test.go # apiserver/facades/client/backups/create.go # apiserver/facades/client/client/client.go # apiserver/facades/client/client/client_test.go # apiserver/facades/client/client/statushistory_test.go # cmd/juju/action/runaction.go # cmd/juju/action/runaction_test.go # cmd/juju/action/waitflag.go # cmd/juju/commands/upgrademodel.go # cmd/juju/commands/upgrademodel_test.go # cmd/jujud/agent/agenttest/agent.go # go.mod # go.sum # mongo/mongo.go # provider/azure/environ.go # provider/azure/export_test.go # provider/azure/storage.go # provider/azure/storage_test.go # scripts/win-installer/setup.iss # snap/snapcraft.yaml # state/backups/backups.go # state/backups/backups_test.go # state/backups/db.go # state/backups/db_dump_test.go # state/backups/export_test.go # state/backups/files_test.go # version/version.go ``` ## QA steps See PRs [JUJU-112]: https://warthogs.atlassian.net/browse/JUJU-112?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [JUJU-136]: https://warthogs.atlassian.net/browse/JUJU-136?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [JUJU-121]: https://warthogs.atlassian.net/browse/JUJU-121?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [JUJU-223]: https://warthogs.atlassian.net/browse/JUJU-223?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [JUJU-227]: https://warthogs.atlassian.net/browse/JUJU-227?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Description
pylibjuju
uses the bundle facade to get the changes for a bundle to be deployed. However, currently theGetChanges
function that the facade uses only handles single-part yaml files, so this PR adds the support for multi-part yaml handling (which the Juju cli uses) to enable the use of overlays in bundles.QA steps
Notes & Discussion
This PR is needed for juju/python-libjuju#566, which adds the support for overlays in deploying bundles.