├── .gitignore ├── src ├── common.rs ├── rest │ ├── mod.rs │ ├── helpers.rs │ ├── netbox.rs │ └── netshot.rs └── main.rs ├── tests └── data │ ├── netshot │ ├── disable_device.json │ ├── single_good_device.json │ ├── search.json │ └── good_device_registration.json │ └── netbox │ ├── ping.json │ ├── single_device_without_primary_ip.json │ ├── single_device_without_name.json │ └── single_good_device.json ├── .rpm └── netbox2netshot.spec ├── Cargo.toml ├── .github └── workflows │ ├── release.yml │ └── build.yml ├── README.md ├── LICENSE └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | /target 3 | tarpaulin-report.* 4 | *.log -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | pub const APP_USER_AGENT: &str = "netbox2netshot"; 2 | -------------------------------------------------------------------------------- /tests/data/netshot/disable_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "DISABLED" 3 | } -------------------------------------------------------------------------------- /src/rest/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod helpers; 2 | pub mod netbox; 3 | pub mod netshot; 4 | -------------------------------------------------------------------------------- /tests/data/netbox/ping.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 0, 3 | "next": null, 4 | "previous": null, 5 | "results": [] 6 | } -------------------------------------------------------------------------------- /tests/data/netshot/single_good_device.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "test-device", 5 | "family": "Nexus 9000 C93108TC-EX", 6 | "mgmtAddress": "1.2.3.4", 7 | "status": "INPRODUCTION" 8 | } 9 | ] -------------------------------------------------------------------------------- /tests/data/netbox/single_device_without_primary_ip.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "url": "http://netbox.example.org/api/dcim/devices/1/", 9 | "name": "test-device-without-primary-ip", 10 | "primary_ip4": null 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /tests/data/netshot/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "query": "[IP] IS 1.2.3.4", 3 | "devices": [ 4 | { 5 | "id": 2318, 6 | "name": "test-device.dc", 7 | "family": "Cisco Catalyst 2900", 8 | "mgmtAddress": "1.2.3.4", 9 | "status": "INPRODUCTION" 10 | }, 11 | { 12 | "id": 2318, 13 | "name": "test-device.dc", 14 | "family": "Cisco Catalyst 2900", 15 | "mgmtAddress": "1.2.3.4", 16 | "status": "INPRODUCTION" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /tests/data/netbox/single_device_without_name.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "url": "http://netbox.example.org/api/dcim/devices/1/", 9 | "name": null, 10 | "primary_ip4": { 11 | "id": 1, 12 | "url": "http://netbox.example.org/api/ipam/ip-addresses/1/", 13 | "family": 4, 14 | "address": "1.2.3.4/32" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /tests/data/netbox/single_good_device.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "next": null, 4 | "previous": null, 5 | "results": [ 6 | { 7 | "id": 1, 8 | "url": "http://netbox.example.org/api/dcim/devices/1/", 9 | "name": "test-device", 10 | "primary_ip4": { 11 | "id": 1, 12 | "url": "http://netbox.example.org/api/ipam/ip-addresses/1/", 13 | "family": 4, 14 | "address": "1.2.3.4/32" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.rpm/netbox2netshot.spec: -------------------------------------------------------------------------------- 1 | %define __spec_install_post %{nil} 2 | %define __os_install_post %{_dbpath}/brp-compress 3 | %define debug_package %{nil} 4 | 5 | Name: netbox2netshot 6 | Summary: Synchronization tool between netbox and netshot 7 | Version: @@VERSION@@ 8 | Release: @@RELEASE@@%{?dist} 9 | License: ASL 2.0 10 | Group: Applications/System 11 | Source0: %{name}-%{version}.tar.gz 12 | 13 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root 14 | 15 | %description 16 | %{summary} 17 | 18 | %prep 19 | %setup -q 20 | 21 | %install 22 | rm -rf %{buildroot} 23 | mkdir -p %{buildroot} 24 | cp -a * %{buildroot} 25 | 26 | %clean 27 | rm -rf %{buildroot} 28 | 29 | %files 30 | %defattr(-,root,root,-) 31 | %{_bindir}/* 32 | -------------------------------------------------------------------------------- /src/rest/helpers.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Error; 2 | use reqwest::Identity; 3 | use std::fs::File; 4 | use std::io::Read; 5 | 6 | /// Create an identity from a private key and certificate registered in a PKCS12 file (with or without password) 7 | pub fn build_identity_from_file( 8 | filename: String, 9 | password: Option, 10 | ) -> Result { 11 | let mut buf = Vec::new(); 12 | File::open(filename.as_str())?.read_to_end(&mut buf)?; 13 | 14 | log::info!("Building identity from {} PFX/P12 file", filename); 15 | let identity = match password { 16 | Some(p) => Identity::from_pkcs12_der(&buf, p.as_str())?, 17 | None => Identity::from_pkcs12_der(&buf, "")?, 18 | }; 19 | 20 | Ok(identity) 21 | } 22 | -------------------------------------------------------------------------------- /tests/data/netshot/good_device_registration.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": ".DiscoverDeviceTypeTask", 3 | "author": "API Token [1: Python Script]", 4 | "changeDate": 1619787406000, 5 | "comments": "Autodiscover device 1.2.3.4", 6 | "creationDate": 1619787406010, 7 | "debugEnabled": false, 8 | "executionDate": null, 9 | "id": 504, 10 | "log": "", 11 | "scheduleReference": 1619787406010, 12 | "scheduleType": "ASAP", 13 | "scheduleFactor": 1, 14 | "status": "SCHEDULED", 15 | "target": "1.2.3.4", 16 | "deviceAddress": { 17 | "prefixLength": 0, 18 | "addressUsage": "PRIMARY", 19 | "ip": "1.2.3.4" 20 | }, 21 | "deviceId": 0, 22 | "snapshotTaskId": 0, 23 | "discoveredDeviceTypeDescription": "Unknown", 24 | "taskDescription": "Device autodiscovery", 25 | "nextExecutionDate": null, 26 | "repeating": false 27 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netbox2netshot" 3 | version = "0.2.0" 4 | authors = ["Mathieu Poussin "] 5 | edition = "2018" 6 | description = "Synchronization tool between netbox and netshot" 7 | license = "Apache-2.0" 8 | readme = "README.md" 9 | repository = "https://github.com/scaleway/netbox2netshot" 10 | keywords = ["netbox", "netshot", "synchronization", "network"] 11 | categories = ["command-line-utilities"] 12 | 13 | [dependencies] 14 | serde = { version = "1.0.125", features = ["derive"]} 15 | clap = { version = "4.0", features = ["derive", "env"] } 16 | log = "0.4" 17 | flexi_logger = "0.19" 18 | reqwest = { version = "0.11", features = ["json", "native-tls", "blocking"]} 19 | anyhow = { version = "1.0", features = ["backtrace"]} 20 | 21 | [dev-dependencies] 22 | mockito = "0.30" 23 | ctor = "0.1.20" 24 | 25 | [package.metadata.rpm] 26 | package = "netbox2netshot" 27 | 28 | [package.metadata.rpm.cargo] 29 | buildflags = ["--release"] 30 | 31 | [package.metadata.rpm.targets] 32 | netbox2netshot = { path = "/usr/bin/netbox2netshot" } 33 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - '*.*.*' 5 | 6 | name: Create release 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-20.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/cache@v2 15 | with: 16 | path: | 17 | ~/.cargo/bin/ 18 | ~/.cargo/registry/index/ 19 | ~/.cargo/registry/cache/ 20 | ~/.cargo/git/db/ 21 | target/ 22 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 23 | - name: Get the version 24 | id: version 25 | run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) 26 | - name: Install cargo plugins 27 | run: cargo install cargo-rpm cargo-deb 28 | continue-on-error: true 29 | - name: Build binary 30 | run: cargo build --release 31 | - name: Build deb package 32 | run: cargo deb 33 | - name: Build rpm package 34 | run: cargo rpm build 35 | 36 | - name: Create release 37 | uses: softprops/action-gh-release@v1 38 | with: 39 | name: Release ${{ github.ref }} 40 | prerelease: false 41 | draft: false 42 | files: | 43 | target/release/netbox2netshot 44 | target/debian/*.deb 45 | target/release/rpmbuild/RPMS/x86_64/*.rpm 46 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - main 8 | push: 9 | branches: 10 | - master 11 | - main 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-20.04 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.cargo/bin/ 25 | ~/.cargo/registry/index/ 26 | ~/.cargo/registry/cache/ 27 | ~/.cargo/git/db/ 28 | target/ 29 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 30 | - name: Install cargo plugins 31 | run: cargo install cargo-rpm cargo-deb 32 | continue-on-error: true 33 | - name: Build 34 | run: cargo build 35 | - name: Build deb package 36 | run: cargo deb 37 | - name: Build rpm package 38 | run: cargo rpm build 39 | - uses: actions/upload-artifact@v4 40 | with: 41 | name: netbox2netshot-${{ github.sha }} 42 | path: target/debug/netbox2netshot 43 | - uses: actions/upload-artifact@v4 44 | with: 45 | name: netbox2netshot-${{ github.sha }}.rpm 46 | path: target/release/rpmbuild/RPMS/x86_64/*.rpm 47 | - uses: actions/upload-artifact@v4 48 | with: 49 | name: netbox2netshot-${{ github.sha }}.deb 50 | path: target/debian/*.deb 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netbox2Netshot 2 | 3 | ## Introduction 4 | 5 | Netbox2Netshot is a tool that allows you to synchronize your [Netbox DCIM](https://github.com/netbox-community/netbox) (using specific criterias) to [Netshot](https://github.com/netfishers-onl/Netshot) so your devices would automatically get backed up by Netshot once added in Netbox. 6 | 7 | The tool is coded in Rust and doesn't required any runtime dependency installed 8 | 9 | ## How to use 10 | 11 | ### Installation 12 | 13 | Gather a pre-built binary, deb or rpm package or install it using Cargo 14 | 15 | ```bash 16 | cargo install netbox2netshot 17 | ``` 18 | 19 | ### Parameters 20 | 21 | Most parameters can be set either via command line arguments or environment variables 22 | 23 | ```bash 24 | netbox2netshot [FLAGS] [OPTIONS] --netbox-url --netshot-domain-id --netshot-token --netshot-url 25 | 26 | FLAGS: 27 | -c, --check Check mode, will not push any change to Netshot 28 | -d, --debug Enable debug/verbose mode 29 | -h, --help Prints help information 30 | -V, --version Prints version information 31 | 32 | OPTIONS: 33 | --netbox-devices-filter 34 | The querystring to use to select the devices from netbox [env: NETBOX_DEVICES_FILTER=] [default: ] 35 | 36 | --netbox-proxy 37 | HTTP(s) proxy to use to connect to Netbox [env: NETBOX_PROXY=] 38 | 39 | --netbox-tls-client-certificate 40 | The TLS certificate to use to authenticate to Netbox (PKCS12 format) [env: NETBOX_TLS_CLIENT_CERTIFICATE=] 41 | 42 | --netbox-tls-client-certificate-password 43 | The optional password for the netbox PKCS12 file [env: NETBOX_TLS_CLIENT_CERTIFICATE_PASSWORD=] 44 | 45 | --netbox-token 46 | The Netbox token [env: NETBOX_TOKEN] 47 | 48 | --netbox-url 49 | The Netbox API URL [env: NETBOX_URL=] 50 | 51 | --netbox-vms-filter 52 | The querystring to use to select the VM from netbox [env: NETBOX_VMS_FILTER=] 53 | 54 | --netshot-domain-id 55 | The domain ID to use when importing a new device [env: NETSHOT_DOMAIN_ID=] 56 | 57 | --netshot-proxy 58 | HTTP(s) proxy to use to connect to Netshot [env: NETSHOT_PROXY=] 59 | 60 | --netshot-tls-client-certificate 61 | The TLS certificate to use to authenticate to Netshot (PKCS12 format) [env: NETSHOT_TLS_CLIENT_CERTIFICATE=] 62 | 63 | --netshot-tls-client-certificate-password 64 | The optional password for the netshot PKCS12 file [env: NETSHOT_TLS_CLIENT_CERTIFICATE_PASSWORD=] 65 | 66 | --netshot-token 67 | The Netshot token [env: NETSHOT_TOKEN] 68 | 69 | --netshot-url 70 | The Netshot API URL [env: NETSHOT_URL=] 71 | ``` 72 | 73 | The query-string format need to be like this (url query string without the `?`): 74 | 75 | ```bash 76 | status=active&platform=cisco-ios&platform=cisco-ios-xe&platform=cisco-ios-xr&platform=cisco-nx-os&platform=juniper-junos&has_primary_ip=true&tenant_group=network 77 | ``` 78 | 79 | If you plan to use TLS authentication, please provide a PKCS12 formatted identity file (.pfx or .p12), they can be created from .pem/.key/.crt using the following command: 80 | ```bash 81 | openssl pkcs12 -export -out my.pfx -inkey my.key -in my.crt 82 | ``` 83 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use anyhow::{Error, Result}; 4 | use flexi_logger::{Duplicate, FileSpec, Logger}; 5 | use clap::Parser; 6 | 7 | use rest::{netbox, netshot}; 8 | 9 | mod common; 10 | mod rest; 11 | 12 | #[derive(Debug, Parser, Clone)] 13 | #[command( 14 | name = "netbox2netshot", 15 | about = "Synchronization tool between netbox and netshot" 16 | )] 17 | struct Opt { 18 | #[arg(short, long, help = "Enable debug/verbose mode")] 19 | debug: bool, 20 | 21 | #[arg(long, help = "The directory to log to", default_value = "logs", env)] 22 | log_directory: String, 23 | 24 | #[arg(long, help = "The Netshot API URL", env)] 25 | netshot_url: String, 26 | 27 | #[arg( 28 | long, 29 | help = "The TLS certificate to use to authenticate to Netshot (PKCS12 format)", 30 | env 31 | )] 32 | netshot_tls_client_certificate: Option, 33 | 34 | #[arg(long, help = "The optional password for the netshot PKCS12 file", env)] 35 | netshot_tls_client_certificate_password: Option, 36 | 37 | #[arg(long, help = "The Netshot token", env, hide_env_values = true)] 38 | netshot_token: String, 39 | 40 | #[arg(long, help = "The domain ID to use when importing a new device", env)] 41 | netshot_domain_id: u32, 42 | 43 | #[arg(long, help = "HTTP(s) proxy to use to connect to Netshot", env)] 44 | netshot_proxy: Option, 45 | 46 | #[arg(long, help = "The Netbox API URL", env)] 47 | netbox_url: String, 48 | 49 | #[arg( 50 | long, 51 | help = "The TLS certificate to use to authenticate to Netbox (PKCS12 format)", 52 | env 53 | )] 54 | netbox_tls_client_certificate: Option, 55 | 56 | #[arg(long, help = "The optional password for the netbox PKCS12 file", env)] 57 | netbox_tls_client_certificate_password: Option, 58 | 59 | #[arg(long, help = "The Netbox token", env, hide_env_values = true)] 60 | netbox_token: Option, 61 | 62 | #[arg( 63 | long, 64 | default_value = "", 65 | help = "The querystring to use to select the devices from netbox", 66 | env 67 | )] 68 | netbox_devices_filter: String, 69 | 70 | #[arg( 71 | long, 72 | help = "The querystring to use to select the VM from netbox", 73 | env 74 | )] 75 | netbox_vms_filter: Option, 76 | 77 | #[arg(long, help = "HTTP(s) proxy to use to connect to Netbox", env)] 78 | netbox_proxy: Option, 79 | 80 | #[arg(short, long, help = "Check mode, will not push any change to Netshot")] 81 | check: bool, 82 | } 83 | 84 | /// Main application entrypoint 85 | fn main() -> Result<(), Error> { 86 | let opt: Opt = Opt::parse(); 87 | let mut logging_level = "info"; 88 | let mut duplicate_level = Duplicate::Info; 89 | if opt.debug { 90 | logging_level = "debug"; 91 | duplicate_level = Duplicate::Debug; 92 | } 93 | 94 | Logger::try_with_str(logging_level)? 95 | .log_to_file(FileSpec::default().directory(opt.clone().log_directory)) 96 | .duplicate_to_stdout(duplicate_level) 97 | .start() 98 | .unwrap(); 99 | 100 | log::info!("Logger initialized with level {}", logging_level); 101 | log::debug!("CLI Parameters : {:#?}", opt); 102 | 103 | let netbox_client = netbox::NetboxClient::new( 104 | opt.netbox_url, 105 | opt.netbox_token, 106 | opt.netbox_proxy, 107 | opt.netbox_tls_client_certificate, 108 | opt.netbox_tls_client_certificate_password, 109 | )?; 110 | netbox_client.ping()?; 111 | 112 | let netshot_client = netshot::NetshotClient::new( 113 | opt.netshot_url, 114 | opt.netshot_token, 115 | opt.netshot_proxy, 116 | opt.netshot_tls_client_certificate, 117 | opt.netshot_tls_client_certificate_password, 118 | )?; 119 | netshot_client.ping()?; 120 | 121 | log::info!("Getting devices list from Netshot"); 122 | let netshot_devices = netshot_client.get_devices(opt.netshot_domain_id)?; 123 | 124 | let netshot_disabled_devices: Vec<&netshot::Device> = netshot_devices 125 | .iter() 126 | .filter(|dev| &dev.status == "DISABLED") 127 | .collect(); 128 | 129 | log::debug!("Building netshot devices simplified inventory"); 130 | let netshot_simplified_inventory: HashMap<&String, &String> = netshot_devices 131 | .iter() 132 | .map(|dev| (&dev.management_address, &dev.name)) 133 | .collect(); 134 | 135 | log::info!("Getting devices list from Netbox"); 136 | let mut netbox_devices = netbox_client.get_devices(&opt.netbox_devices_filter)?; 137 | 138 | if opt.netbox_vms_filter.is_some() { 139 | log::info!("Getting VMS list rom Netbox"); 140 | let mut vms = netbox_client.get_vms(&opt.netbox_vms_filter.unwrap())?; 141 | log::debug!("Merging VMs and Devices lists"); 142 | netbox_devices.append(&mut vms); 143 | } 144 | 145 | log::debug!("Building netbox devices simplified inventory"); 146 | let netbox_simplified_devices: HashMap<_, _> = netbox_devices 147 | .into_iter() 148 | .filter_map(|device| match device.primary_ip4 { 149 | Some(x) => Some(( 150 | x.address.split("/").next().unwrap().to_owned(), 151 | device.name.unwrap_or(device.id.to_string()), 152 | )), 153 | None => { 154 | log::warn!( 155 | "Device {} is missing its primary IP address, skipping it", 156 | device.name.unwrap_or(device.id.to_string()) 157 | ); 158 | None 159 | } 160 | }) 161 | .collect(); 162 | 163 | log::debug!( 164 | "Simplified inventories: Netbox({}), Netshot({})", 165 | netbox_simplified_devices.len(), 166 | netshot_simplified_inventory.len() 167 | ); 168 | 169 | log::debug!("Comparing inventories"); 170 | 171 | let mut devices_to_register: Vec = Vec::new(); 172 | for (ip, hostname) in &netbox_simplified_devices { 173 | match netshot_simplified_inventory.get(ip) { 174 | Some(x) => log::debug!("{}({}) is present on both", x, ip), 175 | None => { 176 | log::debug!("{}({}) missing from Netshot", hostname, ip); 177 | devices_to_register.push(ip.clone()); 178 | } 179 | } 180 | } 181 | 182 | let mut devices_to_disable: Vec = Vec::new(); 183 | for (ip, hostname) in &netshot_simplified_inventory { 184 | match netbox_simplified_devices.get(*ip) { 185 | Some(x) => log::debug!("{}({}) is present on both", x, ip), 186 | None => { 187 | log::debug!("{}({}) to be disabled (missing on Netbox)", hostname, ip); 188 | devices_to_disable.push(ip.to_string()); 189 | } 190 | } 191 | } 192 | 193 | let mut devices_to_enable: Vec = Vec::new(); 194 | for device in &netshot_disabled_devices { 195 | match netbox_simplified_devices.get(&device.management_address) { 196 | Some(_x) => { 197 | log::debug!( 198 | "{}({}) to be enabled (present on Netbox)", 199 | device.name, 200 | device.management_address 201 | ); 202 | devices_to_enable.push(device.management_address.clone()); 203 | } 204 | None => {} 205 | } 206 | } 207 | 208 | log::info!( 209 | "Found {} devices missing on Netshot, to be added", 210 | devices_to_register.len() 211 | ); 212 | log::info!( 213 | "Found {} devices missing on Netbox, to be disabled", 214 | devices_to_disable.len() 215 | ); 216 | log::info!( 217 | "Found {} devices disabled on Netshot but present on Netbox, to be enabled", 218 | devices_to_enable.len() 219 | ); 220 | 221 | if !opt.check { 222 | for device in devices_to_register { 223 | let registration = netshot_client.register_device(device, opt.netshot_domain_id); 224 | if let Err(error) = registration { 225 | log::warn!("Registration failure: {}", error); 226 | } 227 | } 228 | 229 | for device in devices_to_disable { 230 | let registration = netshot_client.disable_device(device); 231 | if let Err(error) = registration { 232 | log::warn!("Disable failure: {}", error); 233 | } 234 | } 235 | for device in devices_to_enable { 236 | let registration = netshot_client.enable_device(device); 237 | if let Err(error) = registration { 238 | log::warn!("Enable failure: {}", error); 239 | } 240 | } 241 | } 242 | Ok(()) 243 | } 244 | 245 | #[cfg(test)] 246 | mod tests { 247 | use flexi_logger::{AdaptiveFormat, Logger}; 248 | 249 | #[ctor::ctor] 250 | fn enable_logging() { 251 | Logger::try_with_str("debug") 252 | .unwrap() 253 | .adaptive_format_for_stderr(AdaptiveFormat::Detailed); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/rest/netbox.rs: -------------------------------------------------------------------------------- 1 | use crate::common::APP_USER_AGENT; 2 | use crate::rest::helpers::build_identity_from_file; 3 | use anyhow::{anyhow, Error, Result}; 4 | use reqwest::header::{HeaderMap, HeaderValue}; 5 | use reqwest::Proxy; 6 | use serde::{Deserialize, Serialize}; 7 | use std::time::Duration; 8 | 9 | const API_LIMIT: u32 = 100; 10 | const PATH_PING: &str = "/api/dcim/devices/?name=netbox2netshot-ping"; 11 | const PATH_DCIM_DEVICES: &str = "/api/dcim/devices/"; 12 | const PATH_VIRT_VM: &str = "/api/virtualization/virtual-machines/"; 13 | 14 | /// The Netbox client 15 | #[derive(Debug)] 16 | pub struct NetboxClient { 17 | pub url: String, 18 | pub token: String, 19 | pub client: reqwest::blocking::Client, 20 | } 21 | 22 | /// Represent the primary_ip field from the DCIM device API call 23 | #[derive(Debug, Serialize, Deserialize)] 24 | pub struct PrimaryIP { 25 | pub id: u32, 26 | pub family: u8, 27 | pub address: String, 28 | } 29 | 30 | /// Represent the required information from the DCIM device API call 31 | #[derive(Debug, Serialize, Deserialize)] 32 | pub struct Device { 33 | pub id: u32, 34 | pub name: Option, 35 | pub primary_ip4: Option, 36 | } 37 | 38 | /// Represent the API response from /api/dcim/devices call 39 | #[derive(Debug, Serialize, Deserialize)] 40 | pub struct NetboxDCIMDeviceList { 41 | count: u32, 42 | next: Option, 43 | previous: Option, 44 | results: Vec, 45 | } 46 | 47 | /// Extract the offset from the URL returned from the API 48 | fn extract_offset(url_string: &String) -> Result { 49 | let url = reqwest::Url::parse(url_string)?; 50 | let offset_string = url.query_pairs().find(|(key, _)| key == "offset"); 51 | match offset_string { 52 | Some((_, x)) => Ok(x.parse()?), 53 | None => Err(anyhow!("No offset found in url")), 54 | } 55 | } 56 | 57 | impl Device { 58 | /// Is this a valid device for import 59 | pub fn is_valid(&self) -> bool { 60 | self.primary_ip4.is_some() && self.name.is_some() 61 | } 62 | } 63 | 64 | impl NetboxClient { 65 | /// Create a client without authentication 66 | pub fn new_anonymous(url: String, proxy: Option) -> Result { 67 | NetboxClient::new(url, None, proxy, None, None) 68 | } 69 | 70 | /// Create a client with the given authentication token 71 | pub fn new( 72 | url: String, 73 | token: Option, 74 | proxy: Option, 75 | tls_client_certificate: Option, 76 | tls_client_certificate_password: Option, 77 | ) -> Result { 78 | log::debug!("Creating new Netbox client to {}", url); 79 | let mut http_client = reqwest::blocking::Client::builder() 80 | .user_agent(APP_USER_AGENT) 81 | .timeout(Duration::from_secs(5)); 82 | 83 | http_client = match token { 84 | Some(ref t) => { 85 | let mut http_headers = HeaderMap::new(); 86 | let header_value = HeaderValue::from_str(format!("Token {}", t).as_str())?; 87 | http_headers.insert("Authorization", header_value); 88 | http_client.default_headers(http_headers) 89 | } 90 | None => http_client, 91 | }; 92 | 93 | http_client = match proxy { 94 | Some(p) => http_client.proxy(Proxy::all(p)?), 95 | None => http_client, 96 | }; 97 | 98 | http_client = match tls_client_certificate { 99 | Some(c) => http_client.identity(build_identity_from_file( 100 | c, 101 | tls_client_certificate_password, 102 | )?), 103 | None => http_client, 104 | }; 105 | 106 | Ok(Self { 107 | url, 108 | token: token.unwrap_or("".to_string()), 109 | client: http_client.build()?, 110 | }) 111 | } 112 | 113 | /// Ping the service to make sure it is reachable and pass the authentication (if there is any) 114 | pub fn ping(&self) -> Result { 115 | let url = format!("{}{}", self.url, PATH_PING); 116 | log::debug!("Pinging {}", url); 117 | let response = self.client.get(url).send()?; 118 | log::debug!("Ping response: {}", response.status()); 119 | Ok(response.status().is_success()) 120 | } 121 | 122 | /// Get a single device page 123 | pub fn get_devices_page( 124 | &self, 125 | path: &str, 126 | query_string: &String, 127 | limit: u32, 128 | offset: u32, 129 | ) -> Result { 130 | let url = format!( 131 | "{}{}?limit={}&offset={}&{}", 132 | self.url, path, limit, offset, query_string 133 | ); 134 | let page: NetboxDCIMDeviceList = self.client.get(url).send()?.json()?; 135 | Ok(page) 136 | } 137 | 138 | /// Get the devices using the given filter 139 | pub fn get_devices(&self, query_string: &String) -> Result, Error> { 140 | let mut devices: Vec = Vec::new(); 141 | let mut offset = 0; 142 | 143 | loop { 144 | let mut response = 145 | self.get_devices_page(PATH_DCIM_DEVICES, &query_string, API_LIMIT, offset)?; 146 | 147 | devices.append(&mut response.results); 148 | 149 | let pages_count = response.count / API_LIMIT; 150 | log::debug!( 151 | "Got {} devices on the {} matches (page {}/{})", 152 | devices.len(), 153 | response.count, 154 | (offset / API_LIMIT), 155 | pages_count 156 | ); 157 | 158 | match response.next { 159 | Some(x) => { 160 | offset = extract_offset(&x)?; 161 | } 162 | None => break, 163 | } 164 | } 165 | 166 | log::info!("Fetched {} devices from Netbox", devices.len()); 167 | Ok(devices) 168 | } 169 | 170 | /// Get the VMs as device using the given filter 171 | pub fn get_vms(&self, query_string: &String) -> Result, Error> { 172 | let mut devices: Vec = Vec::new(); 173 | let mut offset = 0; 174 | 175 | loop { 176 | let mut response = 177 | self.get_devices_page(PATH_VIRT_VM, &query_string, API_LIMIT, offset)?; 178 | 179 | devices.append(&mut response.results); 180 | 181 | let pages_count = response.count / API_LIMIT; 182 | log::debug!( 183 | "Got {} VM devices on the {} matches (page {}/{})", 184 | devices.len(), 185 | response.count, 186 | (offset / API_LIMIT), 187 | pages_count 188 | ); 189 | 190 | match response.next { 191 | Some(x) => { 192 | offset = extract_offset(&x)?; 193 | } 194 | None => break, 195 | } 196 | } 197 | 198 | log::info!("Fetched {} VM devices from Netbox", devices.len()); 199 | Ok(devices) 200 | } 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use super::*; 206 | use mockito; 207 | 208 | #[test] 209 | fn anonymous_initialization() { 210 | let url = mockito::server_url(); 211 | let client = NetboxClient::new_anonymous(url.clone(), None).unwrap(); 212 | assert_eq!(client.token, ""); 213 | assert_eq!(client.url, url); 214 | } 215 | 216 | #[test] 217 | fn authenticated_initialization() { 218 | let url = mockito::server_url(); 219 | let token = String::from("hello"); 220 | let client = NetboxClient::new(url.clone(), Some(token.clone()), None, None, None).unwrap(); 221 | assert_eq!(client.token, token); 222 | assert_eq!(client.url, url); 223 | } 224 | 225 | #[test] 226 | fn failed_ping() { 227 | let url = mockito::server_url(); 228 | 229 | let _mock = mockito::mock("GET", mockito::Matcher::Any) 230 | .with_status(403) 231 | .create(); 232 | 233 | let client = NetboxClient::new_anonymous(url.clone(), None).unwrap(); 234 | let ping = client.ping().unwrap(); 235 | assert_eq!(ping, false); 236 | } 237 | 238 | #[test] 239 | fn successful_ping() { 240 | let url = mockito::server_url(); 241 | 242 | let _mock = mockito::mock("GET", PATH_PING) 243 | .with_body_from_file("tests/data/netbox/ping.json") 244 | .create(); 245 | 246 | let client = NetboxClient::new_anonymous(url.clone(), None).unwrap(); 247 | let ping = client.ping().unwrap(); 248 | assert_eq!(ping, true); 249 | } 250 | 251 | #[test] 252 | fn single_good_device() { 253 | let url = mockito::server_url(); 254 | 255 | let _mock = mockito::mock("GET", PATH_DCIM_DEVICES) 256 | .match_query(mockito::Matcher::Any) 257 | .with_body_from_file("tests/data/netbox/single_good_device.json") 258 | .create(); 259 | 260 | let client = NetboxClient::new_anonymous(url.clone(), None).unwrap(); 261 | let devices = client.get_devices(&String::from("")).unwrap(); 262 | 263 | assert_eq!(devices.len(), 1); 264 | 265 | let device = devices.first().unwrap(); 266 | 267 | assert_eq!(device.name.as_ref().unwrap(), "test-device"); 268 | assert_eq!(device.id, 1 as u32); 269 | assert_eq!(device.primary_ip4.as_ref().unwrap().address, "1.2.3.4/32"); 270 | assert_eq!(device.is_valid(), true); 271 | } 272 | 273 | #[test] 274 | fn single_device_without_primary_ip() { 275 | let url = mockito::server_url(); 276 | 277 | let _mock = mockito::mock("GET", PATH_DCIM_DEVICES) 278 | .match_query(mockito::Matcher::Any) 279 | .with_body_from_file("tests/data/netbox/single_device_without_primary_ip.json") 280 | .create(); 281 | 282 | let client = NetboxClient::new_anonymous(url.clone(), None).unwrap(); 283 | let devices = client.get_devices(&String::from("")).unwrap(); 284 | 285 | assert_eq!(devices.len(), 1); 286 | 287 | let device = devices.first().unwrap(); 288 | 289 | assert_eq!(device.is_valid(), false); 290 | } 291 | 292 | #[test] 293 | fn single_device_without_name() { 294 | let url = mockito::server_url(); 295 | 296 | let _mock = mockito::mock("GET", PATH_DCIM_DEVICES) 297 | .match_query(mockito::Matcher::Any) 298 | .with_body_from_file("tests/data/netbox/single_device_without_name.json") 299 | .create(); 300 | 301 | let client = NetboxClient::new_anonymous(url.clone(), None).unwrap(); 302 | let devices = client.get_devices(&String::from("")).unwrap(); 303 | 304 | assert_eq!(devices.len(), 1); 305 | 306 | let device = devices.first().unwrap(); 307 | 308 | assert_eq!(device.is_valid(), false); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2019 Scaleway. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /src/rest/netshot.rs: -------------------------------------------------------------------------------- 1 | use crate::common::APP_USER_AGENT; 2 | use crate::rest::helpers::build_identity_from_file; 3 | use anyhow::{anyhow, Error, Result}; 4 | use reqwest::header::{HeaderMap, HeaderValue}; 5 | use reqwest::Proxy; 6 | use serde; 7 | use serde::{Deserialize, Serialize}; 8 | use std::time::Duration; 9 | 10 | const PATH_DEVICES: &str = "/api/devices"; 11 | const PATH_DEVICES_SEARCH: &str = "/api/devices/search"; 12 | 13 | #[derive(Debug)] 14 | pub struct NetshotClient { 15 | pub url: String, 16 | pub token: String, 17 | pub client: reqwest::blocking::Client, 18 | } 19 | 20 | #[derive(Debug, Serialize, Deserialize)] 21 | pub struct Device { 22 | pub id: u32, 23 | pub name: String, 24 | #[serde(rename = "mgmtAddress")] 25 | pub management_address: String, 26 | pub status: String, 27 | } 28 | 29 | #[derive(Debug, Serialize, Deserialize)] 30 | struct NewDevicePayload { 31 | #[serde(rename = "autoDiscover")] 32 | auto_discover: bool, 33 | 34 | #[serde(rename = "ipAddress")] 35 | ip_address: String, 36 | 37 | #[serde(rename = "domainId")] 38 | domain_id: u32, 39 | } 40 | 41 | #[derive(Debug, Serialize, Deserialize)] 42 | pub struct NewDeviceCreatedPayload { 43 | #[serde(rename = "id")] 44 | pub task_id: u32, 45 | pub status: String, 46 | } 47 | 48 | #[derive(Debug, Serialize, Deserialize)] 49 | struct UpdateDevicePayload { 50 | enabled: bool, 51 | } 52 | 53 | #[derive(Debug, Serialize, Deserialize)] 54 | pub struct DeviceUpdatedPayload { 55 | pub status: String, 56 | } 57 | 58 | #[derive(Debug, Serialize, Deserialize)] 59 | struct DeviceSearchQueryPayload { 60 | query: String, 61 | } 62 | 63 | #[derive(Debug, Serialize, Deserialize)] 64 | pub struct DeviceSearchResultPayload { 65 | pub query: String, 66 | pub devices: Vec, 67 | } 68 | 69 | impl NetshotClient { 70 | /// Create a client with the given authentication token 71 | pub fn new( 72 | url: String, 73 | token: String, 74 | proxy: Option, 75 | tls_client_certificate: Option, 76 | tls_client_certificate_password: Option, 77 | ) -> Result { 78 | log::debug!("Creating new Netshot client to {}", url); 79 | let mut http_headers = HeaderMap::new(); 80 | let header_value = HeaderValue::from_str(token.as_str())?; 81 | http_headers.insert("X-Netshot-API-Token", header_value); 82 | http_headers.insert("Accept", HeaderValue::from_str("application/json")?); 83 | let mut http_client = reqwest::blocking::Client::builder() 84 | .user_agent(APP_USER_AGENT) 85 | .timeout(Duration::from_secs(5)) 86 | .default_headers(http_headers); 87 | 88 | http_client = match proxy { 89 | Some(p) => http_client.proxy(Proxy::all(p)?), 90 | None => http_client, 91 | }; 92 | 93 | http_client = match tls_client_certificate { 94 | Some(c) => http_client.identity(build_identity_from_file( 95 | c, 96 | tls_client_certificate_password, 97 | )?), 98 | None => http_client, 99 | }; 100 | 101 | Ok(Self { 102 | url, 103 | token, 104 | client: http_client.build()?, 105 | }) 106 | } 107 | 108 | /// To be implemented server side, always return true for now 109 | pub fn ping(&self) -> Result { 110 | log::warn!("Not health check implemented on Netshot, ping will always succeed"); 111 | Ok(true) 112 | } 113 | 114 | /// Get devices registered in Netshot 115 | pub fn get_devices(&self, 116 | domain_id: u32, 117 | ) -> Result, Error> { 118 | let url = format!("{}{}?group={}", self.url, PATH_DEVICES, domain_id); 119 | let devices: Vec = self.client.get(url).send()?.json()?; 120 | 121 | log::debug!("Got {} devices from Netshot", devices.len()); 122 | 123 | Ok(devices) 124 | } 125 | 126 | /// Register a given IP into Netshot and return the corresponding device 127 | pub fn register_device( 128 | &self, 129 | ip_address: String, 130 | domain_id: u32, 131 | ) -> Result { 132 | log::info!("Registering new device with IP {}", ip_address); 133 | 134 | let new_device = NewDevicePayload { 135 | auto_discover: true, 136 | ip_address: ip_address.clone(), 137 | domain_id, 138 | }; 139 | 140 | let url = format!("{}{}", self.url, PATH_DEVICES); 141 | let response = self.client.post(url).json(&new_device).send()?; 142 | 143 | if !response.status().is_success() { 144 | log::warn!( 145 | "Failed to register new device {}, got status {}", 146 | ip_address, 147 | response.status().to_string() 148 | ); 149 | return Err(anyhow!("Failed to register new device {}", ip_address)); 150 | } 151 | 152 | let device_registration: NewDeviceCreatedPayload = response.json()?; 153 | log::debug!( 154 | "Device registration for device {} requested with task ID {}", 155 | ip_address, 156 | device_registration.task_id 157 | ); 158 | 159 | Ok(device_registration) 160 | } 161 | 162 | /// Search for a device 163 | pub fn search_device(&self, query_string: String) -> Result { 164 | let url = format!("{}{}", self.url, PATH_DEVICES_SEARCH); 165 | 166 | let query = DeviceSearchQueryPayload { 167 | query: query_string.clone(), 168 | }; 169 | 170 | let response = self.client.post(url).json(&query).send()?; 171 | 172 | if !response.status().is_success() { 173 | log::warn!( 174 | "Failed to search for device with query `{}`: {}", 175 | query_string.clone(), 176 | response.status().to_string() 177 | ); 178 | return Err(anyhow!( 179 | "Failed to search for device with query: {}", 180 | query_string 181 | )); 182 | } 183 | 184 | let search_result: DeviceSearchResultPayload = response.json()?; 185 | log::debug!( 186 | "Found {} devices with the given search", 187 | search_result.devices.len(), 188 | ); 189 | 190 | Ok(search_result) 191 | } 192 | 193 | /// Set the given device to a given state (enabled/disabled) 194 | fn set_device_enabled( 195 | &self, 196 | ip_address: String, 197 | enabled: bool, 198 | ) -> Result, Error> { 199 | log::info!( 200 | "Setting device with IP {} to enabled={}", 201 | ip_address, 202 | enabled 203 | ); 204 | 205 | let state = UpdateDevicePayload { enabled: enabled }; 206 | 207 | // Search for the device ID 208 | let response = self.search_device(format!("[IP] IS {}", ip_address))?; 209 | let device = response.devices.first().unwrap(); 210 | 211 | if !enabled && device.status == "DISABLED" { 212 | log::warn!( 213 | "Device {}({}) is already disabled, skipping", 214 | device.name, 215 | ip_address 216 | ); 217 | return Ok(Option::None); 218 | } else if enabled && device.status != "DISABLED" { 219 | log::warn!( 220 | "Device {}({}) is already enabled, skipping", 221 | device.name, 222 | ip_address 223 | ); 224 | return Ok(Option::None); 225 | } 226 | 227 | let url = format!("{}{}/{}", self.url, PATH_DEVICES, device.id); 228 | let response = self.client.put(url).json(&state).send()?; 229 | 230 | if !response.status().is_success() { 231 | log::warn!( 232 | "Failed to update state for device {}, got status {}", 233 | ip_address, 234 | response.status().to_string() 235 | ); 236 | return Err(anyhow!( 237 | "Failed to update state for device {}, got status {}", 238 | ip_address, 239 | response.status().to_string() 240 | )); 241 | } 242 | 243 | let device_update: DeviceUpdatedPayload = response.json()?; 244 | log::debug!("Device state of {} set to enabled={}", ip_address, enabled); 245 | 246 | Ok(Option::Some(device_update)) 247 | } 248 | 249 | /// Disable a given device 250 | pub fn disable_device( 251 | &self, 252 | ip_address: String, 253 | ) -> Result, Error> { 254 | self.set_device_enabled(ip_address, false) 255 | } 256 | 257 | /// Enable a given device 258 | pub fn enable_device(&self, ip_address: String) -> Result, Error> { 259 | self.set_device_enabled(ip_address, true) 260 | } 261 | } 262 | 263 | #[cfg(test)] 264 | mod tests { 265 | use super::*; 266 | use mockito; 267 | 268 | #[test] 269 | fn authenticated_initialization() { 270 | let url = mockito::server_url(); 271 | let token = String::from("hello"); 272 | let client = NetshotClient::new(url.clone(), token.clone(), None, None, None).unwrap(); 273 | assert_eq!(client.token, token); 274 | assert_eq!(client.url, url); 275 | } 276 | 277 | #[test] 278 | fn single_good_device() { 279 | let url = mockito::server_url(); 280 | 281 | let _mock = mockito::mock("GET", PATH_DEVICES) 282 | .match_query(mockito::Matcher::Any) 283 | .with_body_from_file("tests/data/netshot/single_good_device.json") 284 | .create(); 285 | 286 | let client = NetshotClient::new(url.clone(), String::new(), None, None, None).unwrap(); 287 | let devices = client.get_devices(1).unwrap(); 288 | 289 | assert_eq!(devices.len(), 1); 290 | 291 | let device = devices.first().unwrap(); 292 | 293 | assert_eq!(device.name, "test-device"); 294 | assert_eq!(device.id, 1 as u32); 295 | assert_eq!(device.management_address, "1.2.3.4"); 296 | } 297 | 298 | #[test] 299 | fn good_device_registration() { 300 | let url = mockito::server_url(); 301 | 302 | let _mock = mockito::mock("POST", PATH_DEVICES) 303 | .match_query(mockito::Matcher::Any) 304 | .match_body(r#"{"autoDiscover":true,"ipAddress":"1.2.3.4","domainId":2}"#) 305 | .with_body_from_file("tests/data/netshot/good_device_registration.json") 306 | .create(); 307 | 308 | let client = NetshotClient::new(url.clone(), String::new(), None, None, None).unwrap(); 309 | let registration = client.register_device(String::from("1.2.3.4"), 2).unwrap(); 310 | 311 | assert_eq!(registration.task_id, 504); 312 | assert_eq!(registration.status, "SCHEDULED"); 313 | } 314 | 315 | #[test] 316 | fn search_devices() { 317 | let url = mockito::server_url(); 318 | 319 | let _mock = mockito::mock("POST", PATH_DEVICES_SEARCH) 320 | .match_query(mockito::Matcher::Any) 321 | .match_body(r#"{"query":"[IP] IS 1.2.3.4"}"#) 322 | .with_body_from_file("tests/data/netshot/search.json") 323 | .create(); 324 | 325 | let client = NetshotClient::new(url.clone(), String::new(), None, None, None).unwrap(); 326 | let result = client 327 | .search_device(String::from("[IP] IS 1.2.3.4")) 328 | .unwrap(); 329 | 330 | assert_eq!(result.devices.len(), 2); 331 | assert_eq!(result.query, "[IP] IS 1.2.3.4"); 332 | } 333 | 334 | #[test] 335 | fn disable_device() { 336 | let url = mockito::server_url(); 337 | 338 | let _mock = mockito::mock("PUT", format!("{}/{}", PATH_DEVICES, 2318).as_str()) 339 | .match_query(mockito::Matcher::Any) 340 | .match_body(r#"{"enabled":false}"#) 341 | .with_body_from_file("tests/data/netshot/disable_device.json") 342 | .create(); 343 | 344 | let _mock2 = mockito::mock("POST", PATH_DEVICES_SEARCH) 345 | .match_query(mockito::Matcher::Any) 346 | .match_body(r#"{"query":"[IP] IS 1.2.3.4"}"#) 347 | .with_body_from_file("tests/data/netshot/search.json") 348 | .create(); 349 | 350 | let client = NetshotClient::new(url.clone(), String::new(), None, None, None).unwrap(); 351 | let registration = client.disable_device(String::from("1.2.3.4")).unwrap(); 352 | 353 | assert_eq!(registration.unwrap().status, "DISABLED"); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.12.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 34 | dependencies = [ 35 | "winapi", 36 | ] 37 | 38 | [[package]] 39 | name = "anstream" 40 | version = "0.6.18" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 43 | dependencies = [ 44 | "anstyle", 45 | "anstyle-parse", 46 | "anstyle-query", 47 | "anstyle-wincon", 48 | "colorchoice", 49 | "is_terminal_polyfill", 50 | "utf8parse", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle" 55 | version = "1.0.10" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 58 | 59 | [[package]] 60 | name = "anstyle-parse" 61 | version = "0.2.6" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 64 | dependencies = [ 65 | "utf8parse", 66 | ] 67 | 68 | [[package]] 69 | name = "anstyle-query" 70 | version = "1.1.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 73 | dependencies = [ 74 | "windows-sys 0.59.0", 75 | ] 76 | 77 | [[package]] 78 | name = "anstyle-wincon" 79 | version = "3.0.7" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 82 | dependencies = [ 83 | "anstyle", 84 | "once_cell", 85 | "windows-sys 0.59.0", 86 | ] 87 | 88 | [[package]] 89 | name = "anyhow" 90 | version = "1.0.92" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" 93 | dependencies = [ 94 | "backtrace", 95 | ] 96 | 97 | [[package]] 98 | name = "assert-json-diff" 99 | version = "2.0.2" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" 102 | dependencies = [ 103 | "serde", 104 | "serde_json", 105 | ] 106 | 107 | [[package]] 108 | name = "atty" 109 | version = "0.2.14" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 112 | dependencies = [ 113 | "hermit-abi 0.1.19", 114 | "libc", 115 | "winapi", 116 | ] 117 | 118 | [[package]] 119 | name = "autocfg" 120 | version = "1.4.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 123 | 124 | [[package]] 125 | name = "backtrace" 126 | version = "0.3.74" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 129 | dependencies = [ 130 | "addr2line", 131 | "cfg-if", 132 | "libc", 133 | "miniz_oxide", 134 | "object", 135 | "rustc-demangle", 136 | "windows-targets 0.52.6", 137 | ] 138 | 139 | [[package]] 140 | name = "base64" 141 | version = "0.21.7" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 144 | 145 | [[package]] 146 | name = "bitflags" 147 | version = "1.3.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 150 | 151 | [[package]] 152 | name = "bitflags" 153 | version = "2.6.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 156 | 157 | [[package]] 158 | name = "bumpalo" 159 | version = "3.16.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 162 | 163 | [[package]] 164 | name = "byteorder" 165 | version = "1.5.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 168 | 169 | [[package]] 170 | name = "bytes" 171 | version = "1.8.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" 174 | 175 | [[package]] 176 | name = "cc" 177 | version = "1.1.34" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" 180 | dependencies = [ 181 | "shlex", 182 | ] 183 | 184 | [[package]] 185 | name = "cfg-if" 186 | version = "1.0.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 189 | 190 | [[package]] 191 | name = "clap" 192 | version = "4.5.27" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" 195 | dependencies = [ 196 | "clap_builder", 197 | "clap_derive", 198 | ] 199 | 200 | [[package]] 201 | name = "clap_builder" 202 | version = "4.5.27" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" 205 | dependencies = [ 206 | "anstream", 207 | "anstyle", 208 | "clap_lex", 209 | "strsim", 210 | ] 211 | 212 | [[package]] 213 | name = "clap_derive" 214 | version = "4.5.24" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" 217 | dependencies = [ 218 | "heck", 219 | "proc-macro2", 220 | "quote", 221 | "syn 2.0.87", 222 | ] 223 | 224 | [[package]] 225 | name = "clap_lex" 226 | version = "0.7.4" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 229 | 230 | [[package]] 231 | name = "colorchoice" 232 | version = "1.0.3" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 235 | 236 | [[package]] 237 | name = "colored" 238 | version = "2.1.0" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" 241 | dependencies = [ 242 | "lazy_static", 243 | "windows-sys 0.48.0", 244 | ] 245 | 246 | [[package]] 247 | name = "core-foundation" 248 | version = "0.9.4" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 251 | dependencies = [ 252 | "core-foundation-sys", 253 | "libc", 254 | ] 255 | 256 | [[package]] 257 | name = "core-foundation-sys" 258 | version = "0.8.7" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 261 | 262 | [[package]] 263 | name = "ctor" 264 | version = "0.1.26" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" 267 | dependencies = [ 268 | "quote", 269 | "syn 1.0.109", 270 | ] 271 | 272 | [[package]] 273 | name = "deranged" 274 | version = "0.3.11" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 277 | dependencies = [ 278 | "powerfmt", 279 | ] 280 | 281 | [[package]] 282 | name = "difference" 283 | version = "2.0.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 286 | 287 | [[package]] 288 | name = "displaydoc" 289 | version = "0.2.5" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 292 | dependencies = [ 293 | "proc-macro2", 294 | "quote", 295 | "syn 2.0.87", 296 | ] 297 | 298 | [[package]] 299 | name = "encoding_rs" 300 | version = "0.8.35" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 303 | dependencies = [ 304 | "cfg-if", 305 | ] 306 | 307 | [[package]] 308 | name = "equivalent" 309 | version = "1.0.1" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 312 | 313 | [[package]] 314 | name = "errno" 315 | version = "0.3.9" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 318 | dependencies = [ 319 | "libc", 320 | "windows-sys 0.52.0", 321 | ] 322 | 323 | [[package]] 324 | name = "fastrand" 325 | version = "2.1.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 328 | 329 | [[package]] 330 | name = "flexi_logger" 331 | version = "0.19.6" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "9b329d8ed052ce3f1e7c9109659d4d999b8f7b1bd2e9d2cecc703cb5a98c8b46" 334 | dependencies = [ 335 | "ansi_term", 336 | "atty", 337 | "glob", 338 | "lazy_static", 339 | "log", 340 | "regex", 341 | "rustversion", 342 | "thiserror", 343 | "time", 344 | ] 345 | 346 | [[package]] 347 | name = "fnv" 348 | version = "1.0.7" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 351 | 352 | [[package]] 353 | name = "foreign-types" 354 | version = "0.3.2" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 357 | dependencies = [ 358 | "foreign-types-shared", 359 | ] 360 | 361 | [[package]] 362 | name = "foreign-types-shared" 363 | version = "0.1.1" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 366 | 367 | [[package]] 368 | name = "form_urlencoded" 369 | version = "1.2.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 372 | dependencies = [ 373 | "percent-encoding", 374 | ] 375 | 376 | [[package]] 377 | name = "futures-channel" 378 | version = "0.3.31" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 381 | dependencies = [ 382 | "futures-core", 383 | ] 384 | 385 | [[package]] 386 | name = "futures-core" 387 | version = "0.3.31" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 390 | 391 | [[package]] 392 | name = "futures-io" 393 | version = "0.3.31" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 396 | 397 | [[package]] 398 | name = "futures-sink" 399 | version = "0.3.31" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 402 | 403 | [[package]] 404 | name = "futures-task" 405 | version = "0.3.31" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 408 | 409 | [[package]] 410 | name = "futures-util" 411 | version = "0.3.31" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 414 | dependencies = [ 415 | "futures-core", 416 | "futures-io", 417 | "futures-task", 418 | "memchr", 419 | "pin-project-lite", 420 | "pin-utils", 421 | "slab", 422 | ] 423 | 424 | [[package]] 425 | name = "getrandom" 426 | version = "0.2.15" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 429 | dependencies = [ 430 | "cfg-if", 431 | "libc", 432 | "wasi", 433 | ] 434 | 435 | [[package]] 436 | name = "gimli" 437 | version = "0.31.1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 440 | 441 | [[package]] 442 | name = "glob" 443 | version = "0.3.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 446 | 447 | [[package]] 448 | name = "h2" 449 | version = "0.3.26" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 452 | dependencies = [ 453 | "bytes", 454 | "fnv", 455 | "futures-core", 456 | "futures-sink", 457 | "futures-util", 458 | "http", 459 | "indexmap", 460 | "slab", 461 | "tokio", 462 | "tokio-util", 463 | "tracing", 464 | ] 465 | 466 | [[package]] 467 | name = "hashbrown" 468 | version = "0.15.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 471 | 472 | [[package]] 473 | name = "heck" 474 | version = "0.5.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 477 | 478 | [[package]] 479 | name = "hermit-abi" 480 | version = "0.1.19" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 483 | dependencies = [ 484 | "libc", 485 | ] 486 | 487 | [[package]] 488 | name = "hermit-abi" 489 | version = "0.3.9" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 492 | 493 | [[package]] 494 | name = "http" 495 | version = "0.2.12" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 498 | dependencies = [ 499 | "bytes", 500 | "fnv", 501 | "itoa", 502 | ] 503 | 504 | [[package]] 505 | name = "http-body" 506 | version = "0.4.6" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 509 | dependencies = [ 510 | "bytes", 511 | "http", 512 | "pin-project-lite", 513 | ] 514 | 515 | [[package]] 516 | name = "httparse" 517 | version = "1.9.5" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 520 | 521 | [[package]] 522 | name = "httpdate" 523 | version = "1.0.3" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 526 | 527 | [[package]] 528 | name = "hyper" 529 | version = "0.14.31" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" 532 | dependencies = [ 533 | "bytes", 534 | "futures-channel", 535 | "futures-core", 536 | "futures-util", 537 | "h2", 538 | "http", 539 | "http-body", 540 | "httparse", 541 | "httpdate", 542 | "itoa", 543 | "pin-project-lite", 544 | "socket2", 545 | "tokio", 546 | "tower-service", 547 | "tracing", 548 | "want", 549 | ] 550 | 551 | [[package]] 552 | name = "hyper-tls" 553 | version = "0.5.0" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" 556 | dependencies = [ 557 | "bytes", 558 | "hyper", 559 | "native-tls", 560 | "tokio", 561 | "tokio-native-tls", 562 | ] 563 | 564 | [[package]] 565 | name = "icu_collections" 566 | version = "1.5.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 569 | dependencies = [ 570 | "displaydoc", 571 | "yoke", 572 | "zerofrom", 573 | "zerovec", 574 | ] 575 | 576 | [[package]] 577 | name = "icu_locid" 578 | version = "1.5.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 581 | dependencies = [ 582 | "displaydoc", 583 | "litemap", 584 | "tinystr", 585 | "writeable", 586 | "zerovec", 587 | ] 588 | 589 | [[package]] 590 | name = "icu_locid_transform" 591 | version = "1.5.0" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 594 | dependencies = [ 595 | "displaydoc", 596 | "icu_locid", 597 | "icu_locid_transform_data", 598 | "icu_provider", 599 | "tinystr", 600 | "zerovec", 601 | ] 602 | 603 | [[package]] 604 | name = "icu_locid_transform_data" 605 | version = "1.5.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 608 | 609 | [[package]] 610 | name = "icu_normalizer" 611 | version = "1.5.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 614 | dependencies = [ 615 | "displaydoc", 616 | "icu_collections", 617 | "icu_normalizer_data", 618 | "icu_properties", 619 | "icu_provider", 620 | "smallvec", 621 | "utf16_iter", 622 | "utf8_iter", 623 | "write16", 624 | "zerovec", 625 | ] 626 | 627 | [[package]] 628 | name = "icu_normalizer_data" 629 | version = "1.5.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 632 | 633 | [[package]] 634 | name = "icu_properties" 635 | version = "1.5.1" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 638 | dependencies = [ 639 | "displaydoc", 640 | "icu_collections", 641 | "icu_locid_transform", 642 | "icu_properties_data", 643 | "icu_provider", 644 | "tinystr", 645 | "zerovec", 646 | ] 647 | 648 | [[package]] 649 | name = "icu_properties_data" 650 | version = "1.5.0" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 653 | 654 | [[package]] 655 | name = "icu_provider" 656 | version = "1.5.0" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 659 | dependencies = [ 660 | "displaydoc", 661 | "icu_locid", 662 | "icu_provider_macros", 663 | "stable_deref_trait", 664 | "tinystr", 665 | "writeable", 666 | "yoke", 667 | "zerofrom", 668 | "zerovec", 669 | ] 670 | 671 | [[package]] 672 | name = "icu_provider_macros" 673 | version = "1.5.0" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 676 | dependencies = [ 677 | "proc-macro2", 678 | "quote", 679 | "syn 2.0.87", 680 | ] 681 | 682 | [[package]] 683 | name = "idna" 684 | version = "1.0.3" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 687 | dependencies = [ 688 | "idna_adapter", 689 | "smallvec", 690 | "utf8_iter", 691 | ] 692 | 693 | [[package]] 694 | name = "idna_adapter" 695 | version = "1.2.0" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 698 | dependencies = [ 699 | "icu_normalizer", 700 | "icu_properties", 701 | ] 702 | 703 | [[package]] 704 | name = "indexmap" 705 | version = "2.6.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 708 | dependencies = [ 709 | "equivalent", 710 | "hashbrown", 711 | ] 712 | 713 | [[package]] 714 | name = "ipnet" 715 | version = "2.10.1" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 718 | 719 | [[package]] 720 | name = "is_terminal_polyfill" 721 | version = "1.70.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 724 | 725 | [[package]] 726 | name = "itoa" 727 | version = "1.0.11" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 730 | 731 | [[package]] 732 | name = "js-sys" 733 | version = "0.3.72" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 736 | dependencies = [ 737 | "wasm-bindgen", 738 | ] 739 | 740 | [[package]] 741 | name = "lazy_static" 742 | version = "1.5.0" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 745 | 746 | [[package]] 747 | name = "libc" 748 | version = "0.2.161" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 751 | 752 | [[package]] 753 | name = "linux-raw-sys" 754 | version = "0.4.14" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 757 | 758 | [[package]] 759 | name = "litemap" 760 | version = "0.7.3" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 763 | 764 | [[package]] 765 | name = "log" 766 | version = "0.4.22" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 769 | 770 | [[package]] 771 | name = "memchr" 772 | version = "2.7.4" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 775 | 776 | [[package]] 777 | name = "mime" 778 | version = "0.3.17" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 781 | 782 | [[package]] 783 | name = "miniz_oxide" 784 | version = "0.8.0" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 787 | dependencies = [ 788 | "adler2", 789 | ] 790 | 791 | [[package]] 792 | name = "mio" 793 | version = "1.0.2" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 796 | dependencies = [ 797 | "hermit-abi 0.3.9", 798 | "libc", 799 | "wasi", 800 | "windows-sys 0.52.0", 801 | ] 802 | 803 | [[package]] 804 | name = "mockito" 805 | version = "0.30.0" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "d10030163d67f681db11810bc486df3149e6d91c8b4f3f96fa8b62b546c2cef8" 808 | dependencies = [ 809 | "assert-json-diff", 810 | "colored", 811 | "difference", 812 | "httparse", 813 | "lazy_static", 814 | "log", 815 | "rand", 816 | "regex", 817 | "serde_json", 818 | "serde_urlencoded", 819 | ] 820 | 821 | [[package]] 822 | name = "native-tls" 823 | version = "0.2.12" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 826 | dependencies = [ 827 | "libc", 828 | "log", 829 | "openssl", 830 | "openssl-probe", 831 | "openssl-sys", 832 | "schannel", 833 | "security-framework", 834 | "security-framework-sys", 835 | "tempfile", 836 | ] 837 | 838 | [[package]] 839 | name = "netbox2netshot" 840 | version = "0.2.0" 841 | dependencies = [ 842 | "anyhow", 843 | "clap", 844 | "ctor", 845 | "flexi_logger", 846 | "log", 847 | "mockito", 848 | "reqwest", 849 | "serde", 850 | ] 851 | 852 | [[package]] 853 | name = "num-conv" 854 | version = "0.1.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 857 | 858 | [[package]] 859 | name = "num_threads" 860 | version = "0.1.7" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 863 | dependencies = [ 864 | "libc", 865 | ] 866 | 867 | [[package]] 868 | name = "object" 869 | version = "0.36.5" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 872 | dependencies = [ 873 | "memchr", 874 | ] 875 | 876 | [[package]] 877 | name = "once_cell" 878 | version = "1.20.2" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 881 | 882 | [[package]] 883 | name = "openssl" 884 | version = "0.10.68" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" 887 | dependencies = [ 888 | "bitflags 2.6.0", 889 | "cfg-if", 890 | "foreign-types", 891 | "libc", 892 | "once_cell", 893 | "openssl-macros", 894 | "openssl-sys", 895 | ] 896 | 897 | [[package]] 898 | name = "openssl-macros" 899 | version = "0.1.1" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 902 | dependencies = [ 903 | "proc-macro2", 904 | "quote", 905 | "syn 2.0.87", 906 | ] 907 | 908 | [[package]] 909 | name = "openssl-probe" 910 | version = "0.1.5" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 913 | 914 | [[package]] 915 | name = "openssl-sys" 916 | version = "0.9.104" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" 919 | dependencies = [ 920 | "cc", 921 | "libc", 922 | "pkg-config", 923 | "vcpkg", 924 | ] 925 | 926 | [[package]] 927 | name = "percent-encoding" 928 | version = "2.3.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 931 | 932 | [[package]] 933 | name = "pin-project-lite" 934 | version = "0.2.15" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 937 | 938 | [[package]] 939 | name = "pin-utils" 940 | version = "0.1.0" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 943 | 944 | [[package]] 945 | name = "pkg-config" 946 | version = "0.3.31" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 949 | 950 | [[package]] 951 | name = "powerfmt" 952 | version = "0.2.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 955 | 956 | [[package]] 957 | name = "ppv-lite86" 958 | version = "0.2.20" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 961 | dependencies = [ 962 | "zerocopy", 963 | ] 964 | 965 | [[package]] 966 | name = "proc-macro2" 967 | version = "1.0.89" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 970 | dependencies = [ 971 | "unicode-ident", 972 | ] 973 | 974 | [[package]] 975 | name = "quote" 976 | version = "1.0.37" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 979 | dependencies = [ 980 | "proc-macro2", 981 | ] 982 | 983 | [[package]] 984 | name = "rand" 985 | version = "0.8.5" 986 | source = "registry+https://github.com/rust-lang/crates.io-index" 987 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 988 | dependencies = [ 989 | "libc", 990 | "rand_chacha", 991 | "rand_core", 992 | ] 993 | 994 | [[package]] 995 | name = "rand_chacha" 996 | version = "0.3.1" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 999 | dependencies = [ 1000 | "ppv-lite86", 1001 | "rand_core", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "rand_core" 1006 | version = "0.6.4" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1009 | dependencies = [ 1010 | "getrandom", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "regex" 1015 | version = "1.11.1" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1018 | dependencies = [ 1019 | "aho-corasick", 1020 | "memchr", 1021 | "regex-automata", 1022 | "regex-syntax", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "regex-automata" 1027 | version = "0.4.8" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 1030 | dependencies = [ 1031 | "aho-corasick", 1032 | "memchr", 1033 | "regex-syntax", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "regex-syntax" 1038 | version = "0.8.5" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1041 | 1042 | [[package]] 1043 | name = "reqwest" 1044 | version = "0.11.27" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1047 | dependencies = [ 1048 | "base64", 1049 | "bytes", 1050 | "encoding_rs", 1051 | "futures-core", 1052 | "futures-util", 1053 | "h2", 1054 | "http", 1055 | "http-body", 1056 | "hyper", 1057 | "hyper-tls", 1058 | "ipnet", 1059 | "js-sys", 1060 | "log", 1061 | "mime", 1062 | "native-tls", 1063 | "once_cell", 1064 | "percent-encoding", 1065 | "pin-project-lite", 1066 | "rustls-pemfile", 1067 | "serde", 1068 | "serde_json", 1069 | "serde_urlencoded", 1070 | "sync_wrapper", 1071 | "system-configuration", 1072 | "tokio", 1073 | "tokio-native-tls", 1074 | "tower-service", 1075 | "url", 1076 | "wasm-bindgen", 1077 | "wasm-bindgen-futures", 1078 | "web-sys", 1079 | "winreg", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "rustc-demangle" 1084 | version = "0.1.24" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1087 | 1088 | [[package]] 1089 | name = "rustix" 1090 | version = "0.38.38" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" 1093 | dependencies = [ 1094 | "bitflags 2.6.0", 1095 | "errno", 1096 | "libc", 1097 | "linux-raw-sys", 1098 | "windows-sys 0.52.0", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "rustls-pemfile" 1103 | version = "1.0.4" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1106 | dependencies = [ 1107 | "base64", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "rustversion" 1112 | version = "1.0.18" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 1115 | 1116 | [[package]] 1117 | name = "ryu" 1118 | version = "1.0.18" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1121 | 1122 | [[package]] 1123 | name = "schannel" 1124 | version = "0.1.26" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" 1127 | dependencies = [ 1128 | "windows-sys 0.59.0", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "security-framework" 1133 | version = "2.11.1" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1136 | dependencies = [ 1137 | "bitflags 2.6.0", 1138 | "core-foundation", 1139 | "core-foundation-sys", 1140 | "libc", 1141 | "security-framework-sys", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "security-framework-sys" 1146 | version = "2.12.0" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" 1149 | dependencies = [ 1150 | "core-foundation-sys", 1151 | "libc", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "serde" 1156 | version = "1.0.214" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" 1159 | dependencies = [ 1160 | "serde_derive", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "serde_derive" 1165 | version = "1.0.214" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" 1168 | dependencies = [ 1169 | "proc-macro2", 1170 | "quote", 1171 | "syn 2.0.87", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "serde_json" 1176 | version = "1.0.132" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" 1179 | dependencies = [ 1180 | "itoa", 1181 | "memchr", 1182 | "ryu", 1183 | "serde", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "serde_urlencoded" 1188 | version = "0.7.1" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1191 | dependencies = [ 1192 | "form_urlencoded", 1193 | "itoa", 1194 | "ryu", 1195 | "serde", 1196 | ] 1197 | 1198 | [[package]] 1199 | name = "shlex" 1200 | version = "1.3.0" 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" 1202 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1203 | 1204 | [[package]] 1205 | name = "slab" 1206 | version = "0.4.9" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1209 | dependencies = [ 1210 | "autocfg", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "smallvec" 1215 | version = "1.13.2" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1218 | 1219 | [[package]] 1220 | name = "socket2" 1221 | version = "0.5.7" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1224 | dependencies = [ 1225 | "libc", 1226 | "windows-sys 0.52.0", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "stable_deref_trait" 1231 | version = "1.2.0" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1234 | 1235 | [[package]] 1236 | name = "strsim" 1237 | version = "0.11.1" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1240 | 1241 | [[package]] 1242 | name = "syn" 1243 | version = "1.0.109" 1244 | source = "registry+https://github.com/rust-lang/crates.io-index" 1245 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1246 | dependencies = [ 1247 | "proc-macro2", 1248 | "quote", 1249 | "unicode-ident", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "syn" 1254 | version = "2.0.87" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 1257 | dependencies = [ 1258 | "proc-macro2", 1259 | "quote", 1260 | "unicode-ident", 1261 | ] 1262 | 1263 | [[package]] 1264 | name = "sync_wrapper" 1265 | version = "0.1.2" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1268 | 1269 | [[package]] 1270 | name = "synstructure" 1271 | version = "0.13.1" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1274 | dependencies = [ 1275 | "proc-macro2", 1276 | "quote", 1277 | "syn 2.0.87", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "system-configuration" 1282 | version = "0.5.1" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1285 | dependencies = [ 1286 | "bitflags 1.3.2", 1287 | "core-foundation", 1288 | "system-configuration-sys", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "system-configuration-sys" 1293 | version = "0.5.0" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1296 | dependencies = [ 1297 | "core-foundation-sys", 1298 | "libc", 1299 | ] 1300 | 1301 | [[package]] 1302 | name = "tempfile" 1303 | version = "3.13.0" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" 1306 | dependencies = [ 1307 | "cfg-if", 1308 | "fastrand", 1309 | "once_cell", 1310 | "rustix", 1311 | "windows-sys 0.59.0", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "thiserror" 1316 | version = "1.0.67" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd" 1319 | dependencies = [ 1320 | "thiserror-impl", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "thiserror-impl" 1325 | version = "1.0.67" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6" 1328 | dependencies = [ 1329 | "proc-macro2", 1330 | "quote", 1331 | "syn 2.0.87", 1332 | ] 1333 | 1334 | [[package]] 1335 | name = "time" 1336 | version = "0.3.36" 1337 | source = "registry+https://github.com/rust-lang/crates.io-index" 1338 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1339 | dependencies = [ 1340 | "deranged", 1341 | "itoa", 1342 | "libc", 1343 | "num-conv", 1344 | "num_threads", 1345 | "powerfmt", 1346 | "serde", 1347 | "time-core", 1348 | "time-macros", 1349 | ] 1350 | 1351 | [[package]] 1352 | name = "time-core" 1353 | version = "0.1.2" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1356 | 1357 | [[package]] 1358 | name = "time-macros" 1359 | version = "0.2.18" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1362 | dependencies = [ 1363 | "num-conv", 1364 | "time-core", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "tinystr" 1369 | version = "0.7.6" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1372 | dependencies = [ 1373 | "displaydoc", 1374 | "zerovec", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "tokio" 1379 | version = "1.41.0" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" 1382 | dependencies = [ 1383 | "backtrace", 1384 | "bytes", 1385 | "libc", 1386 | "mio", 1387 | "pin-project-lite", 1388 | "socket2", 1389 | "windows-sys 0.52.0", 1390 | ] 1391 | 1392 | [[package]] 1393 | name = "tokio-native-tls" 1394 | version = "0.3.1" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1397 | dependencies = [ 1398 | "native-tls", 1399 | "tokio", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "tokio-util" 1404 | version = "0.7.12" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1407 | dependencies = [ 1408 | "bytes", 1409 | "futures-core", 1410 | "futures-sink", 1411 | "pin-project-lite", 1412 | "tokio", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "tower-service" 1417 | version = "0.3.3" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1420 | 1421 | [[package]] 1422 | name = "tracing" 1423 | version = "0.1.40" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1426 | dependencies = [ 1427 | "pin-project-lite", 1428 | "tracing-core", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "tracing-core" 1433 | version = "0.1.32" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1436 | dependencies = [ 1437 | "once_cell", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "try-lock" 1442 | version = "0.2.5" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1445 | 1446 | [[package]] 1447 | name = "unicode-ident" 1448 | version = "1.0.13" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1451 | 1452 | [[package]] 1453 | name = "url" 1454 | version = "2.5.3" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" 1457 | dependencies = [ 1458 | "form_urlencoded", 1459 | "idna", 1460 | "percent-encoding", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "utf16_iter" 1465 | version = "1.0.5" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1468 | 1469 | [[package]] 1470 | name = "utf8_iter" 1471 | version = "1.0.4" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1474 | 1475 | [[package]] 1476 | name = "utf8parse" 1477 | version = "0.2.2" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1480 | 1481 | [[package]] 1482 | name = "vcpkg" 1483 | version = "0.2.15" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1486 | 1487 | [[package]] 1488 | name = "want" 1489 | version = "0.3.1" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1492 | dependencies = [ 1493 | "try-lock", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "wasi" 1498 | version = "0.11.0+wasi-snapshot-preview1" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1501 | 1502 | [[package]] 1503 | name = "wasm-bindgen" 1504 | version = "0.2.95" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 1507 | dependencies = [ 1508 | "cfg-if", 1509 | "once_cell", 1510 | "wasm-bindgen-macro", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "wasm-bindgen-backend" 1515 | version = "0.2.95" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 1518 | dependencies = [ 1519 | "bumpalo", 1520 | "log", 1521 | "once_cell", 1522 | "proc-macro2", 1523 | "quote", 1524 | "syn 2.0.87", 1525 | "wasm-bindgen-shared", 1526 | ] 1527 | 1528 | [[package]] 1529 | name = "wasm-bindgen-futures" 1530 | version = "0.4.45" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" 1533 | dependencies = [ 1534 | "cfg-if", 1535 | "js-sys", 1536 | "wasm-bindgen", 1537 | "web-sys", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "wasm-bindgen-macro" 1542 | version = "0.2.95" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 1545 | dependencies = [ 1546 | "quote", 1547 | "wasm-bindgen-macro-support", 1548 | ] 1549 | 1550 | [[package]] 1551 | name = "wasm-bindgen-macro-support" 1552 | version = "0.2.95" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 1555 | dependencies = [ 1556 | "proc-macro2", 1557 | "quote", 1558 | "syn 2.0.87", 1559 | "wasm-bindgen-backend", 1560 | "wasm-bindgen-shared", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "wasm-bindgen-shared" 1565 | version = "0.2.95" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 1568 | 1569 | [[package]] 1570 | name = "web-sys" 1571 | version = "0.3.72" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 1574 | dependencies = [ 1575 | "js-sys", 1576 | "wasm-bindgen", 1577 | ] 1578 | 1579 | [[package]] 1580 | name = "winapi" 1581 | version = "0.3.9" 1582 | source = "registry+https://github.com/rust-lang/crates.io-index" 1583 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1584 | dependencies = [ 1585 | "winapi-i686-pc-windows-gnu", 1586 | "winapi-x86_64-pc-windows-gnu", 1587 | ] 1588 | 1589 | [[package]] 1590 | name = "winapi-i686-pc-windows-gnu" 1591 | version = "0.4.0" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1594 | 1595 | [[package]] 1596 | name = "winapi-x86_64-pc-windows-gnu" 1597 | version = "0.4.0" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1600 | 1601 | [[package]] 1602 | name = "windows-sys" 1603 | version = "0.48.0" 1604 | source = "registry+https://github.com/rust-lang/crates.io-index" 1605 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1606 | dependencies = [ 1607 | "windows-targets 0.48.5", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "windows-sys" 1612 | version = "0.52.0" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1615 | dependencies = [ 1616 | "windows-targets 0.52.6", 1617 | ] 1618 | 1619 | [[package]] 1620 | name = "windows-sys" 1621 | version = "0.59.0" 1622 | source = "registry+https://github.com/rust-lang/crates.io-index" 1623 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1624 | dependencies = [ 1625 | "windows-targets 0.52.6", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "windows-targets" 1630 | version = "0.48.5" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1633 | dependencies = [ 1634 | "windows_aarch64_gnullvm 0.48.5", 1635 | "windows_aarch64_msvc 0.48.5", 1636 | "windows_i686_gnu 0.48.5", 1637 | "windows_i686_msvc 0.48.5", 1638 | "windows_x86_64_gnu 0.48.5", 1639 | "windows_x86_64_gnullvm 0.48.5", 1640 | "windows_x86_64_msvc 0.48.5", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "windows-targets" 1645 | version = "0.52.6" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1648 | dependencies = [ 1649 | "windows_aarch64_gnullvm 0.52.6", 1650 | "windows_aarch64_msvc 0.52.6", 1651 | "windows_i686_gnu 0.52.6", 1652 | "windows_i686_gnullvm", 1653 | "windows_i686_msvc 0.52.6", 1654 | "windows_x86_64_gnu 0.52.6", 1655 | "windows_x86_64_gnullvm 0.52.6", 1656 | "windows_x86_64_msvc 0.52.6", 1657 | ] 1658 | 1659 | [[package]] 1660 | name = "windows_aarch64_gnullvm" 1661 | version = "0.48.5" 1662 | source = "registry+https://github.com/rust-lang/crates.io-index" 1663 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1664 | 1665 | [[package]] 1666 | name = "windows_aarch64_gnullvm" 1667 | version = "0.52.6" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1670 | 1671 | [[package]] 1672 | name = "windows_aarch64_msvc" 1673 | version = "0.48.5" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1676 | 1677 | [[package]] 1678 | name = "windows_aarch64_msvc" 1679 | version = "0.52.6" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1682 | 1683 | [[package]] 1684 | name = "windows_i686_gnu" 1685 | version = "0.48.5" 1686 | source = "registry+https://github.com/rust-lang/crates.io-index" 1687 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1688 | 1689 | [[package]] 1690 | name = "windows_i686_gnu" 1691 | version = "0.52.6" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1694 | 1695 | [[package]] 1696 | name = "windows_i686_gnullvm" 1697 | version = "0.52.6" 1698 | source = "registry+https://github.com/rust-lang/crates.io-index" 1699 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1700 | 1701 | [[package]] 1702 | name = "windows_i686_msvc" 1703 | version = "0.48.5" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1706 | 1707 | [[package]] 1708 | name = "windows_i686_msvc" 1709 | version = "0.52.6" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1712 | 1713 | [[package]] 1714 | name = "windows_x86_64_gnu" 1715 | version = "0.48.5" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1718 | 1719 | [[package]] 1720 | name = "windows_x86_64_gnu" 1721 | version = "0.52.6" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1724 | 1725 | [[package]] 1726 | name = "windows_x86_64_gnullvm" 1727 | version = "0.48.5" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1730 | 1731 | [[package]] 1732 | name = "windows_x86_64_gnullvm" 1733 | version = "0.52.6" 1734 | source = "registry+https://github.com/rust-lang/crates.io-index" 1735 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1736 | 1737 | [[package]] 1738 | name = "windows_x86_64_msvc" 1739 | version = "0.48.5" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1742 | 1743 | [[package]] 1744 | name = "windows_x86_64_msvc" 1745 | version = "0.52.6" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1748 | 1749 | [[package]] 1750 | name = "winreg" 1751 | version = "0.50.0" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 1754 | dependencies = [ 1755 | "cfg-if", 1756 | "windows-sys 0.48.0", 1757 | ] 1758 | 1759 | [[package]] 1760 | name = "write16" 1761 | version = "1.0.0" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1764 | 1765 | [[package]] 1766 | name = "writeable" 1767 | version = "0.5.5" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1770 | 1771 | [[package]] 1772 | name = "yoke" 1773 | version = "0.7.4" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 1776 | dependencies = [ 1777 | "serde", 1778 | "stable_deref_trait", 1779 | "yoke-derive", 1780 | "zerofrom", 1781 | ] 1782 | 1783 | [[package]] 1784 | name = "yoke-derive" 1785 | version = "0.7.4" 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" 1787 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 1788 | dependencies = [ 1789 | "proc-macro2", 1790 | "quote", 1791 | "syn 2.0.87", 1792 | "synstructure", 1793 | ] 1794 | 1795 | [[package]] 1796 | name = "zerocopy" 1797 | version = "0.7.35" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1800 | dependencies = [ 1801 | "byteorder", 1802 | "zerocopy-derive", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "zerocopy-derive" 1807 | version = "0.7.35" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1810 | dependencies = [ 1811 | "proc-macro2", 1812 | "quote", 1813 | "syn 2.0.87", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "zerofrom" 1818 | version = "0.1.4" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 1821 | dependencies = [ 1822 | "zerofrom-derive", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "zerofrom-derive" 1827 | version = "0.1.4" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 1830 | dependencies = [ 1831 | "proc-macro2", 1832 | "quote", 1833 | "syn 2.0.87", 1834 | "synstructure", 1835 | ] 1836 | 1837 | [[package]] 1838 | name = "zerovec" 1839 | version = "0.10.4" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1842 | dependencies = [ 1843 | "yoke", 1844 | "zerofrom", 1845 | "zerovec-derive", 1846 | ] 1847 | 1848 | [[package]] 1849 | name = "zerovec-derive" 1850 | version = "0.10.3" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1853 | dependencies = [ 1854 | "proc-macro2", 1855 | "quote", 1856 | "syn 2.0.87", 1857 | ] 1858 | --------------------------------------------------------------------------------