Browse Source

Seems to be working now!

We clean up the GOPATH dir, fixing permissions.
Steve Thielemann 1 week ago
parent
commit
7b579dd299
4 changed files with 136 additions and 21 deletions
  1. 1 0
      Cargo.toml
  2. 13 0
      NOTES.md
  3. 30 13
      src/cache.rs
  4. 92 8
      src/main.rs

+ 1 - 0
Cargo.toml

@@ -10,6 +10,7 @@ reqwest = { version = "0.12.15", features = ["blocking", "brotli", "deflate", "g
 url = "2.5.4"
 
 [features]
+# add here so it shows up in vscode (enabled)
 default = ["local-httpbin"]
 local-httpbin = []
 

+ 13 - 0
NOTES.md

@@ -33,3 +33,16 @@ Logging total memory usage:
 ==2470578==   total heap usage: 217,384 allocs, 211,526 frees, 269,243,726 bytes allocated
 ==2470578== 
 ```
+Example run:
+```
+thor@mount-olympus:~/Development/rust/reqwest/go-up$ cargo run -- update
+    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s
+     Running `target/debug/go-up update`
+Version: 1.24.2 [have 1.23.4]
+Downloading newer version...
+Clearing GOROOT /home/thor/go
+Clearing GOPATH /home/thor/gopath
+Extracting...
+Extract cache/go1.24.2.linux-amd64.tar.gz to /home/thor/go
+Updated 1.23.4 to 1.24.2
+```

+ 30 - 13
src/cache.rs

@@ -622,7 +622,6 @@ mod tests {
         let mut dir = testdir!();
         dir.push("cache");
         let temp = dir.clone();
-
         let cache = Cache::new(dir, None).unwrap();
 
         // url_to_basename
@@ -666,6 +665,7 @@ mod tests {
         }
     }
 
+    #[ignore]
     #[test]
     // #[cfg(not(feature = "local-httpbin"))]
     fn remote_test() {
@@ -673,24 +673,24 @@ mod tests {
         dir.push("cache");
 
         // Make a copy of the cache directory PathBuf for verifying paths.
-        let mut t = dir.clone();
+        let mut temp = dir.clone();
 
         let cache = Cache::new(dir, None).unwrap();
         let r = cache.fetch("https://httpbin.org/anything");
 
-        t.push("anything");
+        temp.push("anything");
 
         if let Ok(r) = r {
             if let Status::Fetched(f) = r {
                 assert!(f.exists(), "Cache file exists.");
-                assert_eq!(f, t, "Cache path is what we were expecting.");
-                let mut header_file = t.clone();
+                assert_eq!(f, temp, "Cache path is what we were expecting.");
+                let mut header_file = temp.clone();
                 Cache::append_to_filename(&mut header_file, HEADER_EXT);
 
                 assert!(header_file.exists(), "Cache header file exists.");
-                t.pop();
-                t.push("anything.header");
-                assert_eq!(header_file, t, "Cache header path is what we expected.");
+                temp.pop();
+                temp.push("anything.header");
+                assert_eq!(header_file, temp, "Cache header path is what we expected.");
             } else {
                 panic!("Cache Status is not Status::Fetched, is: {:?}", r);
             }
@@ -732,15 +732,27 @@ mod tests {
             if let Error::HttpErrorStatus(code) = e {
                 assert_eq!(code, 418);
             } else {
-                panic!("Not an ErrorStatus");
+                panic!("Not an ErrorStatus, expected 418");
             }
         } else {
-            panic!("Unexpected error: {r:?}");
+            panic!("Unexpected result: {r:?}");
         }
         // println!("{:?}", r);
 
         let r = cache.fetch("http://127.0.0.1:1024");
-        assert!(r.is_err(), "Confirm connection error");
+        if let Err(e) = r {
+            // Yes, we got an error.  Is it the right kind?
+
+            if let Error::ReqwestError(e) = e {
+                // expected
+                // println!("Yes, reqwest::Error: {:?}", e);
+            } else {
+                panic!("Not the error type we were expecting: {:?}", e);
+            }
+        } else {
+            // Is there something listening to port 1024?  That wasn't expected.
+            panic!("Expected connection error: {:?}", r);
+        }
 
         /*
         I disabled brotli in the Client builder.
@@ -783,11 +795,14 @@ mod tests {
 
         let cache = Cache::new(dir, None).unwrap();
         let etag_url = "http://127.0.0.1/etag/meow";
+        t.push("meow");
 
         let r = cache.fetch(&etag_url);
         if let Ok(r) = r {
             match r {
-                Status::Fetched(_) => {}
+                Status::Fetched(p) => {
+                    assert_eq!(p, t);
+                }
                 _ => {
                     panic!("Expected Status::Fetched on 1st request.");
                 }
@@ -799,7 +814,9 @@ mod tests {
         let r2 = cache.fetch(&etag_url);
         if let Ok(r2) = r2 {
             match r2 {
-                Status::Cached(_) => {}
+                Status::Cached(p) => {
+                    assert_eq!(p, t);
+                }
                 _ => {
                     panic!("Expected Status::Cached on 2nd request.");
                 }

+ 92 - 8
src/main.rs

@@ -2,8 +2,8 @@ use anyhow::{Context, Result, bail};
 use cache::relative_to_absolute;
 use clap::{Parser, Subcommand};
 use std::collections::HashMap;
-use std::env;
-use std::fs::File;
+// use std::env;
+use std::fs::{File, create_dir_all, read_dir, remove_dir_all, set_permissions};
 use std::io::{BufRead, BufReader};
 use std::path::PathBuf;
 use std::process::Command;
@@ -16,7 +16,7 @@ mod cache;
     long_about = "Go Updater
 
 This checks the https://go.dev/dl for newer versions of go.
-It depends upon the GOPATH and GOROOT environment variables being set.
+It depends upon go being in the path, and optionally GOPATH being set.
 
 This can't update a package manager installed version of go (permissions)."
 )]
@@ -64,6 +64,25 @@ fn get_go_env() -> Result<HashMap<String, String>> {
     }
 }
 
+fn extract_tarball(tarball: &str, target: &str) -> Result<()> {
+    println!("Extract {} to {}", tarball, target);
+    let output = Command::new("tar")
+        // Extract, gzipped, from file
+        .arg("-xzf")
+        .arg(tarball)
+        // archive contains go directory. Strip that out.
+        .arg("--strip-components=1")
+        // Set target to extract to.
+        .arg("-C")
+        .arg(target)
+        .output()?;
+    if output.status.success() {
+        return Ok(());
+    }
+    bail!("Extract {} failed.", tarball);
+}
+
+/*
 /// Query `go version`
 #[must_use]
 #[allow(dead_code)]
@@ -76,7 +95,9 @@ fn find_go_version() -> Result<String> {
     }
     bail!("Failed to query go version.");
 }
+*/
 
+/*
 /// Locate the go binary
 ///
 /// This is redundant, it should be located via GO_PATH.
@@ -89,6 +110,7 @@ fn find_go() -> Result<String> {
     }
     bail!("Failed to locate go binary.");
 }
+*/
 
 const GO_URL: &str = "https://go.dev/dl/";
 
@@ -122,8 +144,12 @@ and grab the section of td's.  class=filename has a href, last has SHA256.
  */
 
 // URL: https://go.dev/dl/go1.24.1.linux-amd64.tar.gz
-#[must_use]
+
 /// Get go version from download link.
+///
+/// Given https://go.dev/dl/go1.24.1.linux-amd64.tar.gz
+/// return "1.24.1" version part.
+#[must_use]
 fn version_from_url(url: &str, arch: &str) -> Option<String> {
     if let Some(parts) = url.split_once(arch) {
         if let Some(part) = parts.0.rsplit_once("/go") {
@@ -145,9 +171,9 @@ fn just_href(link: &str) -> Result<String> {
     bail!("Unable to locate href");
 }
 
-/// Find a href link for given os and arch (architecture)
+/// Find a href link for given os and arch (architecture) in a file.
 ///
-/// Look for <a class="download" href="
+/// Look for a line with <a class="download" href="
 #[must_use]
 fn find_arch_link(os_arch: &str, fp: &File) -> Result<String> {
     let reader = BufReader::new(fp);
@@ -171,7 +197,7 @@ fn find_arch_link(os_arch: &str, fp: &File) -> Result<String> {
 ///
 /// Look for <a class="download" href=""
 #[must_use]
-#[deprecated]
+#[deprecated = "Use find_arch_link don't store the HTML in memory."]
 fn find_link(arch: &str) -> Result<String> {
     // , Box<dyn Error>> {
     let fp = File::open(GO_FILE)?;
@@ -193,6 +219,7 @@ fn find_link(arch: &str) -> Result<String> {
 /// Get value from given HashMap<String,String>
 ///
 /// This gives us a String, not a &String, and not an Option.
+/// Returns empty String if key not found.
 fn hashget(hash: &HashMap<String, String>, key: &str) -> String {
     if let Some(value) = hash.get(key) {
         return value.clone();
@@ -200,6 +227,33 @@ fn hashget(hash: &HashMap<String, String>, key: &str) -> String {
     String::new()
 }
 
+fn recursive_remove_readonly(path: &str) -> Result<bool> {
+    let mut result: bool = false;
+    for entry in read_dir(path)? {
+        let fsentry = entry?;
+        if let Ok(meta) = fsentry.metadata() {
+            if meta.is_dir() {
+                if let Ok(r) =
+                    recursive_remove_readonly(&fsentry.path().to_string_lossy().to_string())
+                {
+                    if r {
+                        result = true;
+                    }
+                }
+            }
+            if meta.permissions().readonly() {
+                // println!("Fixing: {}", fsentry.path().display());
+                let mut read = meta.permissions();
+                read.set_readonly(false);
+                // Ok, this is marked readonly, change it.
+                set_permissions(fsentry.path(), read)?;
+                result = true;
+            }
+        }
+    }
+    Ok(result)
+}
+
 fn main() -> Result<()> {
     let cli = Cli::parse();
 
@@ -213,9 +267,12 @@ fn main() -> Result<()> {
     I want to be a bit looser on requiring GOROOT later on.
     */
 
+    /*
     // Get go environment
+    // The old way of doing this.  Using `go env` now.
     let go_path = env::var("GOPATH").unwrap_or(String::new());
     let go_root = env::var("GOROOT").unwrap_or(String::new());
+    */
 
     let go_env = get_go_env().context("Calling `go env` failed.")?;
     let mut go_version = hashget(&go_env, "GOVERSION");
@@ -261,8 +318,35 @@ fn main() -> Result<()> {
                         let latest_status = cache.fetch(&abs)?;
                         let update = latest_status.download_path();
 
+                        println!("Clearing GOROOT {}", go_root);
+                        remove_dir_all(&go_root)?;
+                        create_dir_all(&go_root)?;
+
+                        if !go_path.is_empty() {
+                            if go_path != go_root {
+                                println!("Clearing GOPATH {}", go_path);
+                                // This fails with permissions error
+                                recursive_remove_readonly(&go_path)?;
+                                remove_dir_all(&go_path)?;
+                                create_dir_all(&go_path)?;
+                            }
+                        }
+
+                        println!("Extracting...");
+                        extract_tarball(&update.to_string_lossy().to_string(), &go_root)?;
+                        // Extract go.tar.gz into go_root
+
                         // Ok, we have the update!  Now what?
-                        println!("Ready to install update.");
+                        // Verify new GO version!
+                        let new_go_env = get_go_env().context("Calling `go env` with new go.")?;
+                        let mut new_go_version = hashget(&new_go_env, "GOVERSION");
+                        if new_go_version.starts_with("go") {
+                            new_go_version.remove(0); new_go_version.remove(0);
+                        }
+
+                        println!("Updated {} to {}", go_version, new_go_version);
+
+
                         /*
                         Clear GOROOT.
                         mkdir_all(GOROOT).