-
-
Notifications
You must be signed in to change notification settings - Fork 130
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(node): implement initial support to manage APT repositories #1325
Conversation
8e2c5e1
to
7f8040d
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.
Hi @svengreb!
Thank you so much for your contribution!
I've read through most of the code, but I haven't reviewed the acceptance tests yet. I have a few comments that I hope make sense.
wrapcheck: | ||
ignorePackageGlobs: | ||
# Prevent false-positive matches for errors from packages of the own module. | ||
- github.com/bpg/terraform-provider-proxmox/* |
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 sure if this is a good change. We have logically separate packages under github.com/bpg/terraform-provider-proxmox/, like the API client and the provider itself. Also, wrapping warnings gives a nudge to think about whether the returned error captures enough context to be useful during debugging.
#!/usr/bin/env sh | ||
# An APT repository can be imported using a comma-separated list consisting of the name of the Proxmox VE node, | ||
# the absolute source list file path, and the index in the exact same order, e.g.: | ||
terraform import proxmox_virtual_environment_apt_repository.example pve,/etc/apt/sources.list,0 |
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.
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've read a bit further in the code, the actual ID stored in the state is using _
separator, which would add to the confusion if someone decide to inspect the stored state.
// Note that most constants are exported to allow the usage in (acceptance) tests. | ||
const ( | ||
// SchemaAttrNameComment is the name of the APT repository schema attribute for the associated comment. | ||
SchemaAttrNameComment = "comment" |
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 rather inline all those constants. The legacy code uses attribute name constants extensively, but I found it not super useful.
The constant provides indirect reference in the code, so if value under the constant changes, the places there this value is used through the constant don't need too. But that's the opposite from what we want here!
The schema we define here is very much static. Renaming any of the schema attribute should be done carefully, with necessary ceremonies to ensure backward compatibility.
Then the acceptance tests that validate resource templates against the resource schema and implementation, they should not be automatically changed (via referencing a constant rather than the value) when attribute name changes in the schema. Those tests validates a contract of the published resource schema with the current implementation. So if for some reason an attribute name has changed, I want the test to fail first, then want to se a change in the test / schema to ensure the old resource schema still works with the new resource implementation as well as the new resource schema does (with whatever transformation / deprecation logic as necessary)
Another point is that acceptance tests should also be as much human readable as possible, as they are the main tool for us developers to maintain the code. E.g. here I would need to spend quite a bit of time to understand what the test is doing. I don't see much harm in duplication of the test data (i.e. resource templates) in tests for improving readability.
And lastly "comment"
is much shorter than SchemaAttrNameComment
:)
In most cases the additional context (i.e. 'schema attribute name') is easily deductible form the surrounding code.
rp.ID = types.StringValue( | ||
fmt.Sprintf( | ||
"%s_%s_%s_%d", | ||
ResourceRepoIDPrefix, |
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 don't think we need adding a prefix to the ID value. IDs don't need to be globally unique, and they are scoped per resource type in TF.
"%s_%s_%s_%d", | ||
ResourceRepoIDPrefix, | ||
strings.ToLower(rp.Node.ValueString()), | ||
strings.ToLower(RepoIDCharReplaceRegEx.ReplaceAllString(strings.TrimPrefix(rp.FilePath.ValueString(), "/"), "_")), |
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 quite sure if I understand purpose of theses transformations. Could you perhaps add some examples in the code comments?
|
||
components, convDiags := types.ListValueFrom(ctx, types.StringType, repo.Components) | ||
if convDiags.HasError() { | ||
diags.AddError("Terraform list value conversion", "Convert list of APT repository components") |
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 suggest to avoid "Terraform" wording in the outputs. The provider supports both Terraform and OpenTofu, so the messages specifically pointing to Terraform could be confusing.
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.
Also, would it make more sense to add the actual reported diagnostic error convDiags
to diags
instead of swallowing it?
var ( | ||
// ErrCephVersionNameIllegal indicates an error for an illegal Ceph major version name. | ||
ErrCephVersionNameIllegal = func(name string) error { | ||
return function.NewFuncError(fmt.Sprintf("illegal Ceph major version name %q", name)) |
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 think function.NewFuncError
is misused here. This part of the provider-supplied functions package, returning a specific error implementation.
It also makes the API client code dependent on the hashicorp's code, that I'd prefer to avoid if possible.
I could probably just use the standard golang error wrapping technique, i.e.
var ErrStandardRepoHandleKindIllegal = errors.New("illegal APT standard repository handle kind")
...
return StandardRepoHandleKindUnknown, fmt.Errorf(
"parse APT standard repository handle kind %q: %w",
input, ErrStandardRepoHandleKindIllegal,
)
}
Sure, I'll try to go through the review until the end of this week 😄 |
> Summary This commit implements initial support for managing APT repositories which is (currently) limited to… - …adding "standard" repositories to allow to configure it. - toggling the activation status (enabled/disabled) of any configured repository. + !WARNING! + Note that deleting or modifying a repository in any other way is + (sadly) not possible (yet?)! + The limited functionality is due to the (current) capabilities of + the Proxmox VE APT repository API [1] itself. >> Why are there two resources for one API entity? Even though an APT repository should be seen as a single API entity, it was required to implement standard repositories as dedicated `proxmox_virtual_environment_apt_standard_repository`. This is because standard repositories must be configured (added) first to the default source list files because their activation status can be toggled. This is handled by the HTTP `PUT` request, but the modifying request is `POST` which would require two calls within the same Terraform execution cycle. I tried to implement it in a single resource and it worked out mostly after some handling some edges cases, but in the end there were still too many situations an edge cases where it might break due to Terraform state drifts between states. In the end the dedicated resources are way cleaner and easier to use without no complexity and conditional attribute juggling for practitioners. >> Other "specialties" Unfortunately the Proxmox VE API responses to HTTP `GET` requests with four larger arrays which are, more or less, kind of connected to each other, but they also somehow stand on their own. This means that there is a `files` array that contains the `repositories` again which again contains all repositories with their metadata of every source file. On the other hand available standard repositories are listed in the `standard-repos` array, but their activation status is only stored when they have already been added through a `PUT` request. The `infos` array is more less useless. So in order to get the required data and store them in the state the `importFromAPI` methods of the models must loop through all the deep-nested arrays and act based on specific attributes like a matching file path, comparing it to the activation status and so on. In the end the implementation is really stable after testing it with all possible conditions and state combinations. @bpg if you'd like me to create a small data logic flow chart to make it easier to understand some parts of the code let me know. I can make my local notes "shareable" which I created to not loose track of the logic. >> What is the way to manage the activation status of a "standard" repository? Because the two resources are modular and scoped they can be simply combined to manage an APT "standard" repository, e.g. toggling its activation status. The following examples are also included in the documentations. ```hcl // This resource ensure that the "no-subscription" standard repository // is added to the source list. // It represents the `PUT` API request. resource "proxmox_virtual_environment_apt_standard_repository" "example" { handle = "no-subscription" node = "pve" } // This resource allows to actually modify the activation status of the // standard repository as it represents the `POST`. // Using the values from the dedicated standard repository resource // makes sure that Terraform correctly resolves dependency order. resource "proxmox_virtual_environment_apt_repository" "example" { enabled = true file_path = proxmox_virtual_environment_apt_standard_repository.example.file_path index = proxmox_virtual_environment_apt_standard_repository.example.index node = proxmox_virtual_environment_apt_standard_repository.example.node } ``` [1]: https://pve.proxmox.com/pve-docs/api-viewer/#/nodes/{node}/apt/repositories Signed-off-by: Sven Greb <development@svengreb.de>
7f8040d
to
536f5a2
Compare
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Okay, I'm going to merge it as-is, don't want to let the code rot. Will address remaining comments in the follow up PRs. |
Summary
This pull request implements initial support for managing APT repositories which is (currently) limited to…
Warning
Note that deleting or modifying a repository in any other way is (sadly) not possible (yet?)!
The limited functionality is due to the (current) capabilities of the Proxmox VE APT repository API itself.
Why are there two resources for one API entity?
Even though an APT repository should be seen as a single API entity, it was required to implement standard repositories as dedicated
proxmox_virtual_environment_apt_standard_repository
. This is because standard repositories must be configured (added) first to the default source list files because their activation status can be toggled. This is handled by the HTTPPUT
request, but the modifying request isPOST
which would require two calls within the same Terraform execution cycle. I tried to implement it in a single resource and it worked out mostly after some handling some edges cases, but in the end there were still too many situations an edge cases where it might break due to Terraform state drifts between states. In the end the dedicated resources are way cleaner and easier to use without no complexity and conditional attribute juggling for practitioners.Other "specialties"
Unfortunately the Proxmox VE API responses to HTTP
GET
requests with four larger arrays which are, more or less, kind of connected to each other, but they also somehow stand on their own. This means that there is afiles
array that contains therepositories
again which again contains all repositories with their metadata of every source file. On the other hand available standard repositories are listed in thestandard-repos
array, but their activation status is only stored when they have already been added through aPUT
request. Theinfos
array is more less useless.So in order to get the required data and store them in the state the
importFromAPI
methods of the models must loop through all the deep-nested arrays and act based on specific attributes like a matching file path, comparing it to the activation status and so on.In the end the implementation is really stable after testing it with all possible conditions and state combinations.
@bpg if you'd like me to create a small data logic flow chart to make it easier to understand some parts of the code let me know. I can make my local notes "shareable" which I created to not loose track of the logic.
What is the way to manage the activation status of a "standard" repository?
Because the two resources are modular and scoped they can be simply combined to manage an APT "standard" repository, e.g. toggling its activation status. The following examples are also included in the documentations.
Contributor's Note
/docs
for any user-facing features or additions./fwprovider/nodes/apt
for any new or updated resources / data sources.make example
to verify that the change works as expected.Community Note