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

refactor using library crates #15

Merged
merged 19 commits into from
Sep 18, 2023
Merged

refactor using library crates #15

merged 19 commits into from
Sep 18, 2023

Conversation

nrdxp
Copy link
Owner

@nrdxp nrdxp commented Sep 6, 2023

Resolves #11
Fixes #12

This is a pretty hefty refactor which uses upstream crates to query cloudflare's API and to acquire the users public IP. In addition a basic clap API has been defined to make the required options discoverable directly from the command line. In addition, this adds ipv6 handling and will set both v4 and v6 addresses if found.

Not sure if you want to merge this, or if, as you mentioned elsewhere, you just want to hand over maintainership. Either way is fine by me. Just opening this to have that discussion.

Provides a working devshell and package binary.
Thanks to #7 which could not
be directly applied due to conflict.
DNS resolution from google produced a different IP every time, which
isn't what we want.
Potentially finished but cannot test due to an upstream issue with one
of the endpoints: cloudflare/cloudflare-rs#198
Both the public ip crate, and the cloudflare api crate have small
issues that prevent them from working properly, yet both have open
PRs upstream to fix these issues, so simply patch the crates for now
to get this working.
Allows for specifying the API in an intuitive way, and also
automatically gives us a user help message.
Also add myself as a crate author
Copy link
Collaborator

@colemickens colemickens left a comment

Choose a reason for hiding this comment

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

  1. Feels like a lot of (lines, complexity, etc) of Nix to add. I'm not opposed, but triggers a feeling of, "oh god, should all Rust projects need this much Nix to be a part of the ecosystem". Not a problem or blocker, just a thought I had.

  2. If this fixes paging, can you note that in the README.

Comment on lines +3 to +15

inputs.std.url = "github:divnix/std/v0.24.0-1";
inputs.std.inputs.nixpkgs.follows = "nixpkgs";

inputs.fenix.url = "github:nix-community/fenix";
inputs.crane.url = "github:ipetkov/crane";
inputs.crane.inputs.nixpkgs.follows = "nixpkgs";
inputs.crane.inputs.flake-compat.follows = "";
inputs.crane.inputs.rust-overlay.follows = "";

inputs.std.inputs.devshell.url = "github:numtide/devshell";

inputs.nixpkgs.follows = "fenix/nixpkgs";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Mostly for my knowledge, can you fill me in on why these are all necessary?

Why fenix + crane? (I usually have used fenix to get a nightly toolchain, is that somehow needed here)?

What does std provide?

Copy link
Owner Author

Choose a reason for hiding this comment

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

Sorry I didn't even think much about the Nix, I just used the Rust template that I made for Standard, that I am used to, but I can remove the Nix honestly. That was mostly for my own use, but it does setup a nice devshell with rust-analyzer ready to go for those who use terminal based editors.

Copy link
Collaborator

Choose a reason for hiding this comment

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

doesn't bother me, especially if you're maintaining it :)

src/api.rs Outdated Show resolved Hide resolved
src/api.rs Outdated Show resolved Hide resolved
src/main.rs Outdated Show resolved Hide resolved
@colemickens
Copy link
Collaborator

I'm happy to give you maintainership, whether that means:

  1. redirecting the repo via GitHub mechanisms
  2. adding a huge header noting the change in authoritative repo to the README and github description
  3. adding you as a maintainer here, to colemickens/cfdyndns

Or I can merge this in a bit and keep limping along 🙃.

Let me know what you prefer, I don't have a strong preference, maybe a smidge of a note of origination if we do move the repo, but it's not really something I'm going to lose sleep over.

@nrdxp
Copy link
Owner Author

nrdxp commented Sep 11, 2023

I'm okay with any of the options, although I'd probably prefer to transfer to my user if that's okay with you, as I have more work I'd like to do. Of course I'll mention the proper attributions in the README

@colemickens
Copy link
Collaborator

Some questions:

  1. Am I correct that this will silently ignore any cli.records that don't have corresponding records in cloudflare? I'm not sure whether it's better to loop over cli.records, or whether to just check that something got updated for each record?
  2. Which I guess dovetails into the other question -- what if there are both A and AAAA existing records, but we've only managed to resolve one or the other?

Maybe I'm over-thinking it, but it feels like there's a chance that users could potentially end up in a situation where there's a stale/mismatched record -- imagine I have a laptop, I've previously setup A and AAAA records, and then run cfdyndns from a network where I only have ipv4 connectivity.

It's hard to know what to do without requiring the user to be more explicit about A/AAAA records:

  • should it purge AAAA records if ipv6 didn't resolve?
  • same question for ipv4/A records
  • should it then also add records if they resolve but don't already exist?

I guess it just depends on how comprehensive you want it to be. And I say all of this without really even remembering if the existing code adds missing records, and knowing that it doesn't have any support for AAAA.

@nrdxp
Copy link
Owner Author

nrdxp commented Sep 13, 2023

  1. Am I correct that this will silently ignore any cli.records that don't have corresponding records in cloudflare? I'm not sure whether it's better to loop over cli.records, or whether to just check that something got updated for each record?

Yes, but so did the existing code. I didn't change substantially the code semantics at all, although some rethinking is likely in order at this point, and iterating over the cli.records sounds both more correct, and more efficient. Happy to implement that at this stage.

2. Which I guess dovetails into the other question -- what if there are both A and AAAA existing records, but we've only managed to resolve one or the other?

Only the IP that has been resolved on the localhost will be updated. So if I resolved ipv4 and there is an existing AAAA record, the latter will not be touched. I do see the utility in removing an existing AAAA record though, if only ipv4 is resolved, if only to ensure no stale records remain around that could potentially leave an unintended/insecure route open. To sum up:

  • should it purge AAAA records if ipv6 didn't resolve?
  • same question for ipv4/A records
  • should it then also add records if they resolve but don't already exist?
  • probably
  • same
  • yes

@colemickens
Copy link
Collaborator

Yes, but so did the existing code. I didn't change substantially the code semantics at all,

Ah, well, note to my older, less wise self 😉.

I feel like I just signed you up for more work, but I like the answers. 👍

Also, forgot to say but happy to transfer the repo, and it doesn't have to wait for the merge. Let me know.

Also adds logic to remove stale DNS records.
for sanity & efficiencies sake
@nrdxp
Copy link
Owner Author

nrdxp commented Sep 18, 2023

If you wouldn't mind redirecting at this point to my fork via github that would be great. I implemented the suggestions from our last conversation. So any non-existing records will now be created when a public ip is resolved, and stale records which no longer resolve locally will be deleted. These features are included in the v0.1.1 tag.

@colemickens
Copy link
Collaborator

this is top of my list to review tomorrow.

@colemickens
Copy link
Collaborator

If you wouldn't mind redirecting at this point to my fork via github that would be great. I implemented the suggestions from our last conversation. So any non-existing records will now be created when a public ip is resolved, and stale records which no longer resolve locally will be deleted. These features are included in the v0.1.1 tag.

Just saw this. I was going to transfer the repo to you. Not sure what will happen if I do that if you already have a repo named cfdyndns.

I'll look over this and then, merge and see what happens if I try to transfer.

let mut handles = Vec::with_capacity(cli.records.len());

for (record, zone, dns_v4, dns_v6) in records {
if let Some(zone) = zone {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is mostly a nit, but:

  • check the opposite condition
  • warn if there's a record specified on CLI that doesn't have a matching Zone in CF

Right now, if the user passes a --record that doesn't exist, this silently skips it. As a user, I'd like to be warned, or errored.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(prevents some right-ward drift too)

Copy link
Collaborator

Choose a reason for hiding this comment

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

IMO, I'd probably elide the known domain suffix check too, in this case, and let CF be the arbiter of that. I don't know if CF allows "invalid" suffixes, maybe some folks use CF public DNS for internal names?

Copy link
Owner Author

@nrdxp nrdxp Sep 18, 2023

Choose a reason for hiding this comment

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

Right now, if the user passes a --record that doesn't exist, this silently skips it. As a user, I'd like to be warned, or errored.

actually it should create a new record now, assuming at least the zone exists.

As for style in general, I have more recfactoring planned as well as time allows.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It does create the record, but if I pass --record thisdoesntmatter.thiszonedoesntexist.com then it silently exits with 0, even though there was no Zone found with a matching name. (aka, the second parameter of the tuple is None, and so this falls through)

(no matching Zone means it can't create the record, of course, but the user might want to know they've messed up)

params: ListZonesParams::default(),
};
let zones = client.request(&params);
// HACK: make a second call since dns::Zone does not implement Clone upstream
Copy link
Collaborator

Choose a reason for hiding this comment

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

If I'm not mistaken, I think you can avoid this by cloning the zone_id and only capturing that in the async closure. A quick test shows this builds and seems to still work:

diff --git a/src/fns.rs b/src/fns.rs
index 81ff477..35e6469 100644
--- a/src/fns.rs
+++ b/src/fns.rs
@@ -72,19 +72,18 @@ pub async fn get_records(
 	let params = ListZones {
 		params: ListZonesParams::default(),
 	};
-	let zones = client.request(&params);
-	// HACK: make a second call since dns::Zone does not implement Clone upstream
-	let zones2 = client.request(&params).await?.result;
+	let zones = client.request(&params).await?.result;

-	let mut handles = Vec::with_capacity(zones2.len());
-	let mut records = Vec::with_capacity(zones2.len() * 10);
+	let mut handles = Vec::with_capacity(zones.len());
+	let mut records = Vec::with_capacity(zones.len() * 10);

-	for zone in zones.await?.result {
+	for zone in &zones {
 		let client = client.clone();
+		let zone_id = zone.id.to_string();
 		handles.push(tokio::spawn(async move {
 			client
 				.request(&ListDnsRecords {
-					zone_identifier: &zone.id,
+					zone_identifier: zone_id.to_string().as_ref(),
 					params: ListDnsRecordsParams::default(),
 				})
 				.await
@@ -117,10 +116,10 @@ pub async fn get_records(
 		.map(|r| {
 			(
 				r.to_owned(),
-				zones2
-					.iter()
-					.find(|z| r.contains(&z.name))
-					.map(|z| z.id.to_owned()),
+				zones.iter().find(|z| r.contains(&z.name)).map(|z| {
+					log::info!("{}", z.id.to_owned());
+					z.id.to_owned()
+				}),
 				records
 					.iter()
 					.find(|rec| {

Copy link
Owner Author

@nrdxp nrdxp Sep 18, 2023

Choose a reason for hiding this comment

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

I tried a bunch of ways to try and avoid the second call. I may submit a PR upstream to the cloudflare crate to just implement clone on some of the data types eventually. At least as I have it the first and second call happen asynchronously so as to avoid the overhead as much as possible.

@colemickens
Copy link
Collaborator

I used this as a chance to check my own Rust knowledge. I'm happy to merge as-is.

Do you want to rename your fork so the transfer is ... easier to assume what will happen?

@nrdxp
Copy link
Owner Author

nrdxp commented Sep 18, 2023

Do you want to rename your fork so the transfer is ... easier to assume what will happen?

I renamed to cfdyndns-old

@colemickens colemickens merged commit a52263c into nrdxp:master Sep 18, 2023
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.

allow setting AAAA records (ivp6 support) cfdyndns enable proxy when changing IP
2 participants