-
Notifications
You must be signed in to change notification settings - Fork 908
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
feat(net): provide network config to netplan.State for render #4981
feat(net): provide network config to netplan.State for render #4981
Conversation
# determine default root-dir /etc/netplan and/or specialized | ||
# filenames or read permissions based on whether this config | ||
# contains secrets. | ||
state_output_file._write_yaml_file( |
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.
@slyon points here for awareness:
- cloud-init is calling this private method from netplan in absence of a public netplan API intended to let netplan choose ultimately what file to write and in which directories. Can we consider this a
safe
method to call that won't change until cloud-init is able to call a public method in the future? - If netplan grows the ability in the future, do you expect the functionality to separate sensitive config from world-readable config will live under this method call?
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.
- Yes
- Yes
There is an idea of introducing a public .write_yaml()
method. But we have to work out some kinks first. _write_yaml_file()
is good for now, as its underlying CAPI is a public symbol netplan_state_write_yaml_file()
. And it's also what is being used in the Netplan CLI.
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.
@slyon excellent and thank you. I was wondering what you thought about a feature request for an optional header
param to _write_yaml_file in the future that would allow cloud-init to inject custom comments into the file being written?
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 was already pondering about ways to keep the comments, but that's not currently possible with the underlying libyaml
. I like the idea of a header
parameter.
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.
Another thing that might be helpful is returning the path(s) of any files written so that at the very least we could add our own header after the fact.
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.
lgtm
cloudinit/net/netplan.py
Outdated
cfg_dir = mkdtemp() | ||
try: | ||
with open(os.path.join(cfg_dir, "ci-net-cfg.yaml"), "wb+") as f: | ||
f.write(util.encode_text(net_config_content)) |
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.
note (non-blocking): You could consider doing this all in-memory, using io.StringIO
and/or os.memfd_create
# determine default root-dir /etc/netplan and/or specialized | ||
# filenames or read permissions based on whether this config | ||
# contains secrets. | ||
state_output_file._write_yaml_file( |
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.
- Yes
- Yes
There is an idea of introducing a public .write_yaml()
method. But we have to work out some kinks first. _write_yaml_file()
is good for now, as its underlying CAPI is a public symbol netplan_state_write_yaml_file()
. And it's also what is being used in the Netplan CLI.
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.
Generally LGTM. I do agree with slyon that we should use something like StringIO or perhaps a SpooledTemporaryFile rather than the current tmpdir approach.
Also, will netplan currently write a separate private config for the sensitive parts? If so, I think it's worth a separate integration test ensuring the pieces get written correctly.
cloudinit/net/netplan.py
Outdated
return False | ||
cfg_dir = mkdtemp() | ||
try: | ||
with open(os.path.join(cfg_dir, "ci-net-cfg.yaml"), "wb+") as f: |
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.
Not sure if this will still be relevant if you switch to StringIO, but why are we opening binary if we're writing text? We can pass the encoding to open()
(or just not specify an encoding, but that's considered bad practice) rather than using a utility function.
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 is resolved/dropped as I'm now using SpooledTemporaryFile which defaults to "w+b" and ensuring we are encoding as bytes
# determine default root-dir /etc/netplan and/or specialized | ||
# filenames or read permissions based on whether this config | ||
# contains secrets. | ||
state_output_file._write_yaml_file( |
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.
Another thing that might be helpful is returning the path(s) of any files written so that at the very least we could add our own header after the fact.
cloudinit/net/netplan.py
Outdated
mode = current_mode | ||
util.write_file(fpnplan, content, mode=mode) | ||
if not netplan_api_write_yaml_file(content): | ||
# No netplan api, fallback to write config directly. |
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: As we're adding some extra complexity here, I think the code under this if block
would be better as its own function. fallback_write_yaml_file
or something like that.
Netplan does not currently do that. It's just a card in our backlog so far, see FR-3285 for more details. |
2756d2c
to
0ad5268
Compare
Rely on netplan API, where present, to write network config to 50-cloud-init.yaml. Allow cloud-init to be decoupled from security policies of netplan libraries when rendering potentially senstive network configuratiion. Newer netplan versions may filter sensitive network configuration parts into separate files or directories. Allow cloud-init to rely on netplan's rendering behavior by using netplan.state.State to write_yaml_file with cloud-init's requested base filename: 50-cloud-init.yaml. If netplan security policy changes in the future, those changes will be honored under the hood of netplan.State.
0ad5268
to
416dd69
Compare
I think we can simplify a bit more, can't we? diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
index fe97fdcc9..46cbe3a9d 100644
--- a/cloudinit/net/netplan.py
+++ b/cloudinit/net/netplan.py
@@ -250,11 +250,9 @@ def netplan_api_write_yaml_file(net_config_content: str) -> bool:
CLOUDINIT_NETPLAN_FILE,
)
return False
- cfg_dir = mkdtemp()
try:
- with SpooledTemporaryFile(dir=cfg_dir, mode="w+b") as f:
- f.write(util.encode_text(net_config_content))
- f.flush()
+ with SpooledTemporaryFile(mode="w") as f:
+ f.write(net_config_content)
f.seek(0, io.SEEK_SET)
parser = Parser()
parser.load_yaml(f)
@@ -276,8 +274,6 @@ def netplan_api_write_yaml_file(net_config_content: str) -> bool:
e,
)
return False
- if os.path.exists(cfg_dir):
- shutil.rmtree(cfg_dir)
LOG.debug("Rendered netplan config using netplan python API")
return True
|
@TheRealFalcon correct, we can simplify this. I also adapted our netplan config delta check to allow it to be used for both netplan API and fallback method to ensure we don't run |
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.
+1 to the primary changes, but you have test failures now.
495d08c
to
e7386fa
Compare
Also check netplan config deltas for prior to using netplan API to avoid netplan generate cmd when netplan conf hasn't changed.
e7386fa
to
ddf676d
Compare
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.
LGTM!
Test nit: We don't really have any unit test coverage of the new functionality. But also:
- We have integration test coverage.
- The whole thing is essentially a wrapper to an outside call, so there's not really much value to a unit test.
- It's all wrapped in exceptions to fallback on failure.
Given these 3 things, I'm ok with it.
Proposed Commit Message
Additional Context
Some concerns with this approach:
State._write_yaml_file
doesn't accept a header, strips any inline comments when processing network config yaml. This means that 50-cloud-init.yaml, as rendered by netplan's State._write_yaml will no long contain the helpful comment/header:If we want to take this approach to use netplan API to avoid policy changes in netplan, we probably should look to a feature request for
netplan.State._write_yaml_file
to allow a header or footer parameter to allow extending the config content that is written by netplan.Test Steps
Checklist
Merge type