inital working commit
This commit is contained in:
commit
28109122b2
18 changed files with 3321 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
target
|
||||||
|
result
|
||||||
|
.direnv
|
||||||
|
|
||||||
7
.ignore
Normal file
7
.ignore
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Cargo.lock
|
||||||
|
flake.lock
|
||||||
|
|
||||||
|
# Same as gitignore:
|
||||||
|
target
|
||||||
|
result
|
||||||
|
.direnv
|
||||||
2121
Cargo.lock
generated
Normal file
2121
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
39
Cargo.toml
Normal file
39
Cargo.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
[package]
|
||||||
|
name = "quick-start"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# THE kubernetes api
|
||||||
|
kube = { version = "2.0.1", features = ["runtime", "derive"] }
|
||||||
|
k8s-openapi = { version = "0.26.0", features = ["latest", "schemars"] }
|
||||||
|
|
||||||
|
schemars = { version = "1" }
|
||||||
|
|
||||||
|
# Tokio
|
||||||
|
tokio = { version = "1.14.0", features = ["full"] }
|
||||||
|
tokio-stream = { version = "0.1.9", features = ["net"] }
|
||||||
|
|
||||||
|
# Error tracing?
|
||||||
|
tracing = "0.1.36"
|
||||||
|
tracing-subscriber = "0.3.17"
|
||||||
|
|
||||||
|
# Needed to parse data to yaml in kubectl
|
||||||
|
serde = "1.0.130"
|
||||||
|
serde_json = "1.0.68"
|
||||||
|
serde_yaml = "0.9.19"
|
||||||
|
serde-value = "0.7.0"
|
||||||
|
|
||||||
|
# .with_context()
|
||||||
|
# Should be a error handling and context library
|
||||||
|
anyhow = "1.0.71"
|
||||||
|
# Open the $EDITOR
|
||||||
|
edit = "0.1.3"
|
||||||
|
# CLI arg parser
|
||||||
|
clap = { version = "4.0", default-features = false, features = ["std", "cargo", "derive"] }
|
||||||
|
|
||||||
|
futures = { version = "0.3.17", default-features = false }
|
||||||
|
either = "1.6.1"
|
||||||
2
deny.toml
Normal file
2
deny.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[licenses]
|
||||||
|
allow = ["MIT"]
|
||||||
116
flake.lock
generated
Normal file
116
flake.lock
generated
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"advisory-db": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763814576,
|
||||||
|
"narHash": "sha256-5ifBs3miZT21d6tkoKKoqOnUOJulnbvFPPIv5f7DV9I=",
|
||||||
|
"owner": "rustsec",
|
||||||
|
"repo": "advisory-db",
|
||||||
|
"rev": "f2c79ffdfaea4c86200d2b848fdfbd382847ff2f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rustsec",
|
||||||
|
"repo": "advisory-db",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"crane": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763511871,
|
||||||
|
"narHash": "sha256-KKZWi+ij7oT0Ag8yC6MQkzfHGcytyjMJDD+47ZV1YNU=",
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"rev": "099f9014bc8d0cd6e445470ea1df0fd691d5a548",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "ipetkov",
|
||||||
|
"repo": "crane",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": []
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763793715,
|
||||||
|
"narHash": "sha256-wyYhBaCo+m76TegkIG0qBK4dPo0McWtWTjcaf2vwzvI=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "ad1e51a518dc3f1e4e52890b8f11bd434de6008f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763618868,
|
||||||
|
"narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"advisory-db": "advisory-db",
|
||||||
|
"crane": "crane",
|
||||||
|
"fenix": "fenix",
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
142
flake.nix
Normal file
142
flake.nix
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
{
|
||||||
|
description = "Build a cargo project";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
|
||||||
|
crane.url = "github:ipetkov/crane";
|
||||||
|
|
||||||
|
fenix = {
|
||||||
|
url = "github:nix-community/fenix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
inputs.rust-analyzer-src.follows = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
advisory-db = {
|
||||||
|
url = "github:rustsec/advisory-db";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, crane, fenix, flake-utils, advisory-db, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
|
craneLib = crane.mkLib pkgs;
|
||||||
|
src = craneLib.cleanCargoSource ./.;
|
||||||
|
|
||||||
|
# Common arguments can be set here to avoid repeating them later
|
||||||
|
commonArgs = {
|
||||||
|
inherit src;
|
||||||
|
strictDeps = true;
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
# Add additional build inputs here
|
||||||
|
] ++ lib.optionals pkgs.stdenv.isDarwin [
|
||||||
|
# Additional darwin specific inputs can be set here
|
||||||
|
pkgs.libiconv
|
||||||
|
];
|
||||||
|
|
||||||
|
# Additional environment variables can be set directly
|
||||||
|
# MY_CUSTOM_VAR = "some value";
|
||||||
|
};
|
||||||
|
|
||||||
|
craneLibLLvmTools = craneLib.overrideToolchain
|
||||||
|
(fenix.packages.${system}.complete.withComponents [
|
||||||
|
"cargo"
|
||||||
|
"llvm-tools"
|
||||||
|
"rustc"
|
||||||
|
]);
|
||||||
|
|
||||||
|
# Build *just* the cargo dependencies, so we can reuse
|
||||||
|
# all of that work (e.g. via cachix) when running in CI
|
||||||
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
|
|
||||||
|
# Build the actual crate itself, reusing the dependency
|
||||||
|
# artifacts from above.
|
||||||
|
my-crate = craneLib.buildPackage (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = {
|
||||||
|
# Build the crate as part of `nix flake check` for convenience
|
||||||
|
inherit my-crate;
|
||||||
|
|
||||||
|
# Run clippy (and deny all warnings) on the crate source,
|
||||||
|
# again, reusing the dependency artifacts from above.
|
||||||
|
#
|
||||||
|
# Note that this is done as a separate derivation so that
|
||||||
|
# we can block the CI if there are issues here, but not
|
||||||
|
# prevent downstream consumers from building our crate by itself.
|
||||||
|
my-crate-clippy = craneLib.cargoClippy (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
cargoClippyExtraArgs = "--all-targets -- --deny warnings";
|
||||||
|
});
|
||||||
|
|
||||||
|
my-crate-doc = craneLib.cargoDoc (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
|
||||||
|
# Check formatting
|
||||||
|
my-crate-fmt = craneLib.cargoFmt {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
|
||||||
|
my-crate-toml-fmt = craneLib.taploFmt {
|
||||||
|
src = pkgs.lib.sources.sourceFilesBySuffices src [ ".toml" ];
|
||||||
|
# taplo arguments can be further customized below as needed
|
||||||
|
# taploExtraArgs = "--config ./taplo.toml";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Audit dependencies
|
||||||
|
my-crate-audit = craneLib.cargoAudit {
|
||||||
|
inherit src advisory-db;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Audit licenses
|
||||||
|
my-crate-deny = craneLib.cargoDeny {
|
||||||
|
inherit src;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Run tests with cargo-nextest
|
||||||
|
# Consider setting `doCheck = false` on `my-crate` if you do not want
|
||||||
|
# the tests to run twice
|
||||||
|
my-crate-nextest = craneLib.cargoNextest (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
partitions = 1;
|
||||||
|
partitionType = "count";
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
packages = {
|
||||||
|
default = my-crate;
|
||||||
|
} // lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
|
||||||
|
my-crate-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // {
|
||||||
|
inherit cargoArtifacts;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apps.default = flake-utils.lib.mkApp {
|
||||||
|
drv = my-crate;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.default = craneLib.devShell {
|
||||||
|
# Inherit inputs from checks.
|
||||||
|
checks = self.checks.${system};
|
||||||
|
|
||||||
|
# Additional dev-shell environment variables can be set directly
|
||||||
|
# MY_CUSTOM_DEVELOPMENT_VAR = "something else";
|
||||||
|
|
||||||
|
# Extra inputs can be added here; cargo and rustc are provided by default.
|
||||||
|
packages = [
|
||||||
|
pkgs.rust-analyzer
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
292
src/main.rs
Normal file
292
src/main.rs
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
//! This is a simple imitation of the basic functionality of kubectl:
|
||||||
|
//! kubectl {get, delete, apply, watch, edit} <resource> [name]
|
||||||
|
//! with labels and namespace selectors supported.
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use futures::{StreamExt, TryStreamExt};
|
||||||
|
use k8s_openapi::{
|
||||||
|
api::apps::v1::Deployment, apimachinery::pkg::apis::meta::v1::Time, chrono::Utc,
|
||||||
|
};
|
||||||
|
use kube::{
|
||||||
|
api::{Api, DynamicObject, ListParams, Patch, PatchParams, ResourceExt},
|
||||||
|
core::GroupVersionKind,
|
||||||
|
discovery::{ApiCapabilities, ApiResource, Discovery, Scope},
|
||||||
|
runtime::{
|
||||||
|
wait::{await_condition, conditions::is_deleted},
|
||||||
|
watcher, WatchStreamExt,
|
||||||
|
},
|
||||||
|
Client,
|
||||||
|
};
|
||||||
|
use tracing::*;
|
||||||
|
|
||||||
|
mod packets;
|
||||||
|
mod types;
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
struct App {
|
||||||
|
#[arg(long, short, default_value_t = OutputMode::Pretty)]
|
||||||
|
output: OutputMode,
|
||||||
|
#[arg(long, short)]
|
||||||
|
file: Option<std::path::PathBuf>,
|
||||||
|
#[arg(long, short = 'l')]
|
||||||
|
selector: Option<String>,
|
||||||
|
#[arg(long, short)]
|
||||||
|
namespace: Option<String>,
|
||||||
|
#[arg(long, short = 'A')]
|
||||||
|
all: bool,
|
||||||
|
verb: Verb,
|
||||||
|
resource: Option<String>,
|
||||||
|
name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, clap::ValueEnum)]
|
||||||
|
enum OutputMode {
|
||||||
|
Pretty,
|
||||||
|
Yaml,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputMode {
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Pretty => "pretty",
|
||||||
|
Self::Yaml => "yaml",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for OutputMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.pad(self.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, clap::ValueEnum)]
|
||||||
|
enum Verb {
|
||||||
|
Get,
|
||||||
|
Delete,
|
||||||
|
Edit,
|
||||||
|
Watch,
|
||||||
|
Apply,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_api_resource(
|
||||||
|
discovery: &Discovery,
|
||||||
|
name: &str,
|
||||||
|
) -> Option<(ApiResource, ApiCapabilities)> {
|
||||||
|
// iterate through groups to find matching kind/plural names at recommended versions
|
||||||
|
// and then take the minimal match by group.name (equivalent to sorting groups by group.name).
|
||||||
|
// this is equivalent to kubectl's api group preference
|
||||||
|
discovery
|
||||||
|
.groups()
|
||||||
|
.flat_map(|group| {
|
||||||
|
group
|
||||||
|
.resources_by_stability()
|
||||||
|
.into_iter()
|
||||||
|
.map(move |res| (group, res))
|
||||||
|
})
|
||||||
|
.filter(|(_, (res, _))| {
|
||||||
|
// match on both resource name and kind name
|
||||||
|
// ideally we should allow shortname matches as well
|
||||||
|
name.eq_ignore_ascii_case(&res.kind) || name.eq_ignore_ascii_case(&res.plural)
|
||||||
|
})
|
||||||
|
.min_by_key(|(group, _res)| group.name())
|
||||||
|
.map(|(_, res)| res)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
async fn get(&self, api: Api<DynamicObject>, lp: ListParams) -> Result<()> {
|
||||||
|
let mut result: Vec<_> = if let Some(n) = &self.name {
|
||||||
|
vec![api.get(n).await?]
|
||||||
|
} else {
|
||||||
|
api.list(&lp).await?.items
|
||||||
|
};
|
||||||
|
result
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|x| x.managed_fields_mut().clear()); // hide managed fields
|
||||||
|
|
||||||
|
match self.output {
|
||||||
|
OutputMode::Yaml => println!("{}", serde_yaml::to_string(&result)?),
|
||||||
|
OutputMode::Pretty => {
|
||||||
|
// Display style; size columns according to longest name
|
||||||
|
let max_name = result
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.name_any().len() + 2)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(63);
|
||||||
|
println!("{0:<width$} {1:<20}", "NAME", "AGE", width = max_name);
|
||||||
|
for inst in result {
|
||||||
|
let age = inst
|
||||||
|
.creation_timestamp()
|
||||||
|
.map(format_creation)
|
||||||
|
.unwrap_or_default();
|
||||||
|
println!(
|
||||||
|
"{0:<width$} {1:<20}",
|
||||||
|
inst.name_any(),
|
||||||
|
age,
|
||||||
|
width = max_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, api: Api<DynamicObject>, lp: ListParams) -> Result<()> {
|
||||||
|
if let Some(n) = &self.name {
|
||||||
|
if let either::Either::Left(pdel) = api.delete(n, &Default::default()).await? {
|
||||||
|
// await delete before returning
|
||||||
|
await_condition(api, n, is_deleted(&pdel.uid().unwrap())).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
api.delete_collection(&Default::default(), &lp).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn watch(&self, api: Api<DynamicObject>, mut wc: watcher::Config) -> Result<()> {
|
||||||
|
if let Some(n) = &self.name {
|
||||||
|
wc = wc.fields(&format!("metadata.name={n}"));
|
||||||
|
}
|
||||||
|
// present a dumb table for it for now. kubectl does not do this anymore.
|
||||||
|
let mut stream = watcher(api, wc).applied_objects().boxed();
|
||||||
|
println!("{0:<width$} {1:<20}", "NAME", "AGE", width = 63);
|
||||||
|
while let Some(inst) = stream.try_next().await? {
|
||||||
|
let age = inst
|
||||||
|
.creation_timestamp()
|
||||||
|
.map(format_creation)
|
||||||
|
.unwrap_or_default();
|
||||||
|
println!("{0:<width$} {1:<20}", inst.name_any(), age, width = 63);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn edit(&self, api: Api<DynamicObject>) -> Result<()> {
|
||||||
|
if let Some(n) = &self.name {
|
||||||
|
let mut orig = api.get(n).await?;
|
||||||
|
orig.managed_fields_mut().clear(); // hide managed fields
|
||||||
|
let input = serde_yaml::to_string(&orig)?;
|
||||||
|
debug!("opening {} in {:?}", orig.name_any(), edit::get_editor());
|
||||||
|
let edited = edit::edit(&input)?;
|
||||||
|
if edited != input {
|
||||||
|
info!("updating changed object {}", orig.name_any());
|
||||||
|
let data: DynamicObject = serde_yaml::from_str(&edited)?;
|
||||||
|
// NB: simplified kubectl constructs a merge-patch of differences
|
||||||
|
api.replace(n, &Default::default(), &data).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("need a name to edit");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn apply(&self, client: Client, discovery: &Discovery) -> Result<()> {
|
||||||
|
let ssapply = PatchParams::apply("kubectl-light").force();
|
||||||
|
let pth = self.file.clone().expect("apply needs a -f file supplied");
|
||||||
|
let yaml = std::fs::read_to_string(&pth)
|
||||||
|
.with_context(|| format!("Failed to read {}", pth.display()))?;
|
||||||
|
for doc in multidoc_deserialize(&yaml)? {
|
||||||
|
let obj: DynamicObject = serde_yaml::from_value(doc)?;
|
||||||
|
let namespace = obj
|
||||||
|
.metadata
|
||||||
|
.namespace
|
||||||
|
.as_deref()
|
||||||
|
.or(self.namespace.as_deref());
|
||||||
|
let gvk = if let Some(tm) = &obj.types {
|
||||||
|
GroupVersionKind::try_from(tm)?
|
||||||
|
} else {
|
||||||
|
bail!("cannot apply object without valid TypeMeta {:?}", obj);
|
||||||
|
};
|
||||||
|
let name = obj.name_any();
|
||||||
|
if let Some((ar, caps)) = discovery.resolve_gvk(&gvk) {
|
||||||
|
let api = dynamic_api(ar, caps, client.clone(), namespace, false);
|
||||||
|
trace!("Applying {}: \n{}", gvk.kind, serde_yaml::to_string(&obj)?);
|
||||||
|
let data: serde_json::Value = serde_json::to_value(&obj)?;
|
||||||
|
let _r = api.patch(&name, &ssapply, &Patch::Apply(data)).await?;
|
||||||
|
info!("applied {} {}", gvk.kind, name);
|
||||||
|
} else {
|
||||||
|
warn!("Cannot apply document for unknown {:?}", gvk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
// let app: App = clap::Parser::parse();
|
||||||
|
|
||||||
|
let kubeconfig = kube::config::Kubeconfig::read()?;
|
||||||
|
let client = Client::try_from(kubeconfig)?;
|
||||||
|
|
||||||
|
let deployments: Api<Deployment> = Api::default_namespaced(client);
|
||||||
|
|
||||||
|
let lp: ListParams = ListParams::default();
|
||||||
|
println!("{:?}", deployments.list(&lp).await?);
|
||||||
|
|
||||||
|
// // discovery (to be able to infer apis from kind/plural only)
|
||||||
|
// let discovery = Discovery::new(client.clone()).run().await?;
|
||||||
|
|
||||||
|
// // Defer to methods for verbs
|
||||||
|
// if let Some(resource) = &app.resource {
|
||||||
|
// // Common discovery, parameters, and api configuration for a single resource
|
||||||
|
// let (ar, caps) = resolve_api_resource(&discovery, resource)
|
||||||
|
// .with_context(|| format!("resource {resource:?} not found in cluster"))?;
|
||||||
|
// let mut lp = ListParams::default();
|
||||||
|
// if let Some(label) = &app.selector {
|
||||||
|
// lp = lp.labels(label);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let mut wc = watcher::Config::default();
|
||||||
|
// if let Some(label) = &app.selector {
|
||||||
|
// wc = wc.labels(label);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// let api = dynamic_api(ar, caps, client, app.namespace.as_deref(), app.all);
|
||||||
|
|
||||||
|
// tracing::info!(?app.verb, ?resource, name = ?app.name.clone().unwrap_or_default(), "requested objects");
|
||||||
|
// match app.verb {
|
||||||
|
// Verb::Edit => app.edit(api).await?,
|
||||||
|
// Verb::Get => app.get(api, lp).await?,
|
||||||
|
// Verb::Delete => app.delete(api, lp).await?,
|
||||||
|
// Verb::Watch => app.watch(api, wc).await?,
|
||||||
|
// Verb::Apply => bail!("verb {:?} cannot act on an explicit resource", app.verb),
|
||||||
|
// }
|
||||||
|
// } else if app.verb == Verb::Apply {
|
||||||
|
// app.apply(client, &discovery).await? // multi-resource special behaviour
|
||||||
|
// }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dynamic_api(
|
||||||
|
ar: ApiResource,
|
||||||
|
caps: ApiCapabilities,
|
||||||
|
client: Client,
|
||||||
|
ns: Option<&str>,
|
||||||
|
all: bool,
|
||||||
|
) -> Api<DynamicObject> {
|
||||||
|
if caps.scope == Scope::Cluster || all {
|
||||||
|
Api::all_with(client, &ar)
|
||||||
|
} else if let Some(namespace) = ns {
|
||||||
|
Api::namespaced_with(client, namespace, &ar)
|
||||||
|
} else {
|
||||||
|
Api::default_namespaced_with(client, &ar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_creation(time: Time) -> String {
|
||||||
|
let dur = Utc::now().signed_duration_since(time.0);
|
||||||
|
match (dur.num_days(), dur.num_hours(), dur.num_minutes()) {
|
||||||
|
(days, _, _) if days > 0 => format!("{days}d"),
|
||||||
|
(_, hours, _) if hours > 0 => format!("{hours}h"),
|
||||||
|
(_, _, mins) => format!("{mins}m"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multidoc_deserialize(data: &str) -> Result<Vec<serde_yaml::Value>> {
|
||||||
|
use serde::Deserialize;
|
||||||
|
let mut docs = vec![];
|
||||||
|
for de in serde_yaml::Deserializer::from_str(data) {
|
||||||
|
docs.push(serde_yaml::Value::deserialize(de)?);
|
||||||
|
}
|
||||||
|
Ok(docs)
|
||||||
|
}
|
||||||
41
src/packets/clientbound/login.rs
Normal file
41
src/packets/clientbound/login.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packets::{Packet, SendPacket},
|
||||||
|
types::VarString,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// id: 0x00
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Disconnect {
|
||||||
|
reason: VarString,
|
||||||
|
all: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Disconnect {
|
||||||
|
pub fn parse(packet: Packet) -> Option<Disconnect> {
|
||||||
|
let mut reader = packet.data.into_iter();
|
||||||
|
Some(Disconnect {
|
||||||
|
all: packet.all,
|
||||||
|
reason: VarString::parse(&mut reader)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn get_string(&self) -> String {
|
||||||
|
self.reason.get_value()
|
||||||
|
}
|
||||||
|
pub fn set_reason(reason: String) -> Option<Disconnect> {
|
||||||
|
let vec = VarString::from(reason).move_data()?;
|
||||||
|
Disconnect::parse(Packet::from_bytes(0, vec)?)
|
||||||
|
}
|
||||||
|
pub fn get_all(&self) -> Vec<u8> {
|
||||||
|
self.all.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPacket for Disconnect {
|
||||||
|
fn send_packet(&self, stream: &mut std::net::TcpStream) -> std::io::Result<()> {
|
||||||
|
stream.write_all(&self.all)?;
|
||||||
|
stream.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/packets/clientbound/mod.rs
Normal file
2
src/packets/clientbound/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod login;
|
||||||
|
pub mod status;
|
||||||
155
src/packets/clientbound/status.rs
Normal file
155
src/packets/clientbound/status.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
use std::{collections::HashMap, io::Write};
|
||||||
|
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packets::{Packet, SendPacket},
|
||||||
|
types::VarString,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait StatusTrait {
|
||||||
|
fn get_players_online(&self) -> i32;
|
||||||
|
fn set_description(&mut self, str: String);
|
||||||
|
fn get_description(&mut self) -> &mut String;
|
||||||
|
fn get_string(&self) -> String;
|
||||||
|
}
|
||||||
|
impl StatusTrait for StatusStructNew {
|
||||||
|
fn get_players_online(&self) -> i32
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.players.online
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_description(&mut self, str: String)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.description.text = str;
|
||||||
|
}
|
||||||
|
fn get_description(&mut self) -> &mut String
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
&mut self.description.text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_string(&self) -> String {
|
||||||
|
serde_json::to_string(&self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusTrait for StatusStructOld {
|
||||||
|
fn get_players_online(&self) -> i32 {
|
||||||
|
self.players.online
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_description(&mut self, str: String) {
|
||||||
|
self.description = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_description(&mut self) -> &mut String {
|
||||||
|
&mut self.description
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_string(&self) -> String {
|
||||||
|
serde_json::to_string(&self).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct StatusStructNew {
|
||||||
|
pub version: StatusVersion,
|
||||||
|
pub enforcesSecureChat: Option<bool>,
|
||||||
|
pub description: StatusDescription,
|
||||||
|
pub players: StatusPlayers,
|
||||||
|
#[serde(flatten)]
|
||||||
|
extra: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct StatusStructOld {
|
||||||
|
pub version: StatusVersion,
|
||||||
|
pub description: String,
|
||||||
|
pub players: StatusPlayers,
|
||||||
|
#[serde(flatten)]
|
||||||
|
extra: HashMap<String, Value>,
|
||||||
|
}
|
||||||
|
impl StatusStructNew {
|
||||||
|
pub fn create() -> StatusStructNew {
|
||||||
|
StatusStructNew {
|
||||||
|
version: StatusVersion {
|
||||||
|
name: "???".to_owned(),
|
||||||
|
protocol: -1,
|
||||||
|
},
|
||||||
|
enforcesSecureChat: Some(false),
|
||||||
|
description: StatusDescription {
|
||||||
|
text: "Proxy default config".to_owned(),
|
||||||
|
},
|
||||||
|
players: StatusPlayers { max: 0, online: 0 },
|
||||||
|
extra: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct StatusDescription {
|
||||||
|
pub text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct StatusVersion {
|
||||||
|
pub name: String,
|
||||||
|
pub protocol: i32,
|
||||||
|
}
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct StatusPlayers {
|
||||||
|
pub max: i32,
|
||||||
|
pub online: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// id: 0x00
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StatusResponse {
|
||||||
|
json: VarString,
|
||||||
|
all: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusResponse {
|
||||||
|
pub fn parse(packet: Packet) -> Option<StatusResponse> {
|
||||||
|
let mut reader = packet.data.into_iter();
|
||||||
|
Some(StatusResponse {
|
||||||
|
all: packet.all,
|
||||||
|
json: VarString::parse(&mut reader)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn get_string(&self) -> String {
|
||||||
|
self.json.get_value()
|
||||||
|
}
|
||||||
|
pub fn get_json(&self) -> Option<Box<dyn StatusTrait>> {
|
||||||
|
if let Some(json) = serde_json::from_str::<StatusStructNew>(&self.json.get_value()).ok() {
|
||||||
|
return Some(Box::new(json));
|
||||||
|
} else if let Some(json) =
|
||||||
|
serde_json::from_str::<StatusStructOld>(&self.json.get_value()).ok()
|
||||||
|
{
|
||||||
|
return Some(Box::new(json));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
pub fn set_json(json: Box<dyn StatusTrait>) -> StatusResponse {
|
||||||
|
let vec = VarString::from(json.get_string()).move_data().unwrap();
|
||||||
|
StatusResponse::parse(Packet::from_bytes(0, vec).unwrap()).unwrap()
|
||||||
|
}
|
||||||
|
pub fn get_all(&self) -> Vec<u8> {
|
||||||
|
self.all.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPacket for StatusResponse {
|
||||||
|
fn send_packet(&self, stream: &mut std::net::TcpStream) -> std::io::Result<()> {
|
||||||
|
stream.write_all(&self.all)?;
|
||||||
|
stream.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/packets/mod.rs
Normal file
120
src/packets/mod.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
use mc_proxy::{types::*, ProtocolState};
|
||||||
|
use std::{
|
||||||
|
io::{self, Read, Write},
|
||||||
|
net::TcpStream,
|
||||||
|
};
|
||||||
|
pub mod clientbound;
|
||||||
|
pub mod serverbound;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Packet {
|
||||||
|
pub id: VarInt,
|
||||||
|
length: VarInt,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
pub all: Vec<u8>,
|
||||||
|
}
|
||||||
|
pub trait SendPacket {
|
||||||
|
fn send_packet(&self, stream: &mut TcpStream) -> io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPacket for Packet {
|
||||||
|
fn send_packet(&self, stream: &mut TcpStream) -> io::Result<()> {
|
||||||
|
stream.write_all(&self.all)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Packet {
|
||||||
|
pub fn from_bytes(id: i32, data: Vec<u8>) -> Option<Packet> {
|
||||||
|
let id = VarInt::from(id)?;
|
||||||
|
let length = VarInt::from((data.len() + id.get_data().len()) as i32)?;
|
||||||
|
let mut all = length.get_data();
|
||||||
|
all.append(&mut id.get_data());
|
||||||
|
all.append(&mut data.clone());
|
||||||
|
Some(Packet {
|
||||||
|
id,
|
||||||
|
length,
|
||||||
|
data,
|
||||||
|
all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn new(id: i32, data: Vec<u8>) -> Option<Packet> {
|
||||||
|
let mut vec = VarInt::from(id)?.get_data();
|
||||||
|
vec.append(&mut data.clone());
|
||||||
|
|
||||||
|
let mut all = VarInt::from(vec.len() as i32)?.get_data();
|
||||||
|
all.append(&mut vec.clone());
|
||||||
|
all.append(&mut data.clone());
|
||||||
|
Some(Packet {
|
||||||
|
id: VarInt::from(id)?,
|
||||||
|
length: VarInt::from(vec.len() as i32)?,
|
||||||
|
data,
|
||||||
|
all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn parse(buf: &mut TcpStream) -> Option<Packet> {
|
||||||
|
let bytes_iter = &mut buf.bytes().into_iter().map(|x| x.unwrap());
|
||||||
|
let length = VarInt::parse(bytes_iter)?;
|
||||||
|
// println!("---length: {length}");
|
||||||
|
let id = match VarInt::parse(bytes_iter) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
println!("Packet id problem(it was None)! REEEEEEEEEEEEEEEEEEEE");
|
||||||
|
panic!();
|
||||||
|
// return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// println!("---id: {id}");
|
||||||
|
if id.get_int() == 122 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data: Vec<u8> = vec![0; length.get_int() as usize - id.get_data().len()];
|
||||||
|
match buf.read_exact(&mut data) {
|
||||||
|
Ok(_) => {
|
||||||
|
// data_id.append(&mut data.clone());
|
||||||
|
// data_length.append(&mut data_id);
|
||||||
|
let mut vec = id.get_data();
|
||||||
|
vec.append(&mut data.clone());
|
||||||
|
let mut all = length.get_data();
|
||||||
|
all.append(&mut vec);
|
||||||
|
Some(Packet {
|
||||||
|
id,
|
||||||
|
length,
|
||||||
|
data,
|
||||||
|
all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(x) => {
|
||||||
|
if id.get_int() == 122 {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
println!("len = {}: {:?}", length.get_int(), length.get_data());
|
||||||
|
println!("Buffer read error: {x}");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn all(&self) -> Option<Vec<u8>> {
|
||||||
|
let mut vec = self.id.get_data();
|
||||||
|
vec.append(&mut self.data.clone());
|
||||||
|
let mut all = VarInt::from(vec.len() as i32)?.get_data();
|
||||||
|
all.append(&mut vec);
|
||||||
|
return Some(all);
|
||||||
|
}
|
||||||
|
pub fn proto_name(&self, state: &ProtocolState) -> String {
|
||||||
|
match state {
|
||||||
|
ProtocolState::Handshaking => match self.id.get_int() {
|
||||||
|
0 => "Handshake".to_owned(),
|
||||||
|
_ => "error".to_owned(),
|
||||||
|
},
|
||||||
|
ProtocolState::Status => match self.id.get_int() {
|
||||||
|
0 => "StatusRequest".to_owned(),
|
||||||
|
1 => "PingRequest".to_owned(),
|
||||||
|
_ => "error".to_owned(),
|
||||||
|
},
|
||||||
|
_ => "Dont care state".to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/packets/serverbound/handshake.rs
Normal file
70
src/packets/serverbound/handshake.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use nix::NixPath;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
packets::{Packet, SendPacket},
|
||||||
|
types::{UShort, VarInt, VarString},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// id: 0x00
|
||||||
|
pub struct Handshake {
|
||||||
|
pub protocol_version: VarInt,
|
||||||
|
pub server_address: VarString,
|
||||||
|
pub server_port: UShort,
|
||||||
|
pub next_state: VarInt,
|
||||||
|
all: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handshake {
|
||||||
|
pub fn parse(packet: Packet) -> Option<Handshake> {
|
||||||
|
let mut reader = packet.data.clone().into_iter();
|
||||||
|
let protocol_version = VarInt::parse(&mut reader)?;
|
||||||
|
let server_address = VarString::parse(&mut reader)?;
|
||||||
|
let server_port = UShort::parse(&mut reader)?;
|
||||||
|
let next_state = VarInt::parse(&mut reader)?;
|
||||||
|
Some(Handshake {
|
||||||
|
protocol_version,
|
||||||
|
server_address,
|
||||||
|
server_port,
|
||||||
|
next_state,
|
||||||
|
all: packet.all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn get_server_address(&self) -> String {
|
||||||
|
self.server_address.get_value()
|
||||||
|
}
|
||||||
|
pub fn get_next_state(&self) -> i32 {
|
||||||
|
self.next_state.get_int()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(
|
||||||
|
protocol_version: VarInt,
|
||||||
|
server_address: VarString,
|
||||||
|
server_port: UShort,
|
||||||
|
next_state: VarInt,
|
||||||
|
) -> Option<Handshake> {
|
||||||
|
let mut vec = VarInt::from(0)?.get_data();
|
||||||
|
vec.append(&mut protocol_version.get_data());
|
||||||
|
vec.append(&mut server_address.get_data()?);
|
||||||
|
vec.append(&mut server_port.get_data());
|
||||||
|
vec.append(&mut next_state.get_data());
|
||||||
|
let mut all = VarInt::from(vec.len() as i32)?.get_data();
|
||||||
|
all.append(&mut vec);
|
||||||
|
Some(Handshake {
|
||||||
|
protocol_version,
|
||||||
|
server_address,
|
||||||
|
server_port,
|
||||||
|
next_state,
|
||||||
|
all,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPacket for Handshake {
|
||||||
|
fn send_packet(&self, stream: &mut std::net::TcpStream) -> std::io::Result<()> {
|
||||||
|
stream.write_all(&self.all)?;
|
||||||
|
stream.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/packets/serverbound/mod.rs
Normal file
2
src/packets/serverbound/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod handshake;
|
||||||
|
pub mod status;
|
||||||
22
src/packets/serverbound/status.rs
Normal file
22
src/packets/serverbound/status.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use crate::packets::{Packet, SendPacket};
|
||||||
|
|
||||||
|
/// id: 0x00
|
||||||
|
pub struct StatusRequest {
|
||||||
|
all: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatusRequest {
|
||||||
|
pub fn parse(packet: Packet) -> Option<StatusRequest> {
|
||||||
|
Some(StatusRequest { all: packet.all })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendPacket for StatusRequest {
|
||||||
|
fn send_packet(&self, stream: &mut std::net::TcpStream) -> std::io::Result<()> {
|
||||||
|
stream.write_all(&self.all)?;
|
||||||
|
stream.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
172
src/types/mod.rs
Normal file
172
src/types/mod.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
const SEGMENT_BITS: u8 = 0x7F;
|
||||||
|
const CONTINUE_BIT: u8 = 0x80;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VarInt {
|
||||||
|
value: i32,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for VarInt {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarInt {
|
||||||
|
pub fn get_int(&self) -> i32 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clones the data for use, the sturct is still usable.
|
||||||
|
pub fn get_data(&self) -> Vec<u8> {
|
||||||
|
self.data.clone()
|
||||||
|
}
|
||||||
|
/// Moves the data out from the struct. Struct is useless later.
|
||||||
|
pub fn move_data(self) -> Vec<u8> {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
pub fn read<I>(data: &mut I) -> Option<i32>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = u8>,
|
||||||
|
{
|
||||||
|
let mut value: i32 = 0;
|
||||||
|
let mut position = 0;
|
||||||
|
|
||||||
|
for current_byte in data {
|
||||||
|
value |= ((current_byte & SEGMENT_BITS) as i32) << position;
|
||||||
|
|
||||||
|
if current_byte & CONTINUE_BIT == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
position += 7;
|
||||||
|
|
||||||
|
if position > 32 {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
pub fn parse<I>(reader: &mut I) -> Option<VarInt>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = u8>,
|
||||||
|
{
|
||||||
|
let mut value: i32 = 0;
|
||||||
|
let mut position = 0;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
|
||||||
|
for current_byte in reader {
|
||||||
|
let current_byte = current_byte;
|
||||||
|
vec.push(current_byte);
|
||||||
|
value |= ((current_byte & SEGMENT_BITS) as i32) << position;
|
||||||
|
|
||||||
|
if current_byte & CONTINUE_BIT == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
position += 7;
|
||||||
|
|
||||||
|
if position > 32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(VarInt { value, data: vec })
|
||||||
|
}
|
||||||
|
pub fn from(num: i32) -> Option<VarInt> {
|
||||||
|
Some(VarInt {
|
||||||
|
value: num,
|
||||||
|
data: VarInt::write_varint(num)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn write_varint(num: i32) -> Option<Vec<u8>> {
|
||||||
|
let mut num = num;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
if num == 0 {
|
||||||
|
vec.push(0);
|
||||||
|
}
|
||||||
|
while num != 0 {
|
||||||
|
vec.push(num as u8 & SEGMENT_BITS);
|
||||||
|
num = num >> 7;
|
||||||
|
if num != 0 {
|
||||||
|
let a = vec.pop()?;
|
||||||
|
vec.push(a | CONTINUE_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VarString {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarString {
|
||||||
|
pub fn get_value(&self) -> String {
|
||||||
|
self.value.clone()
|
||||||
|
}
|
||||||
|
pub fn move_data(self) -> Option<Vec<u8>> {
|
||||||
|
let mut vec = VarInt::from(self.value.len() as i32)?.move_data();
|
||||||
|
vec.append(&mut (Vec::from(self.value.as_bytes())));
|
||||||
|
Some(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_data(&self) -> Option<Vec<u8>> {
|
||||||
|
let mut vec = VarInt::from(self.value.len() as i32)?.move_data();
|
||||||
|
vec.append(&mut (Vec::from(self.value.as_bytes())));
|
||||||
|
Some(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(string: String) -> VarString {
|
||||||
|
VarString { value: string }
|
||||||
|
}
|
||||||
|
pub fn parse<I>(data: &mut I) -> Option<VarString>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = u8>,
|
||||||
|
{
|
||||||
|
let length = VarInt::read(data)?;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
for _ in 0..length {
|
||||||
|
vec.push(data.next()?);
|
||||||
|
}
|
||||||
|
Some(VarString {
|
||||||
|
value: String::from_utf8(vec).ok()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UShort {
|
||||||
|
value: u16,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl UShort {
|
||||||
|
pub fn get_value(&self) -> u16 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
pub fn get_data(&self) -> Vec<u8> {
|
||||||
|
self.data.clone()
|
||||||
|
}
|
||||||
|
pub fn parse<I>(data: &mut I) -> Option<UShort>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = u8>,
|
||||||
|
{
|
||||||
|
let mut vec = vec![data.next()?];
|
||||||
|
let mut int: u16 = vec[0] as u16;
|
||||||
|
int = int << 8;
|
||||||
|
vec.push(data.next()?);
|
||||||
|
int |= vec[1] as u16;
|
||||||
|
Some(UShort {
|
||||||
|
value: int,
|
||||||
|
data: vec,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn from(short: u16) -> UShort {
|
||||||
|
let mut vec = vec![(short >> 8) as u8];
|
||||||
|
vec.push(((short >> 8) << 8) as u8);
|
||||||
|
UShort {
|
||||||
|
value: short,
|
||||||
|
data: vec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
taplo.toml
Normal file
13
taplo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Sorts `Cargo.toml` dependencies. All other `.toml` files are formatted with the default config.
|
||||||
|
#
|
||||||
|
# https://taplo.tamasfe.dev/configuration/file.html#configuration-file
|
||||||
|
|
||||||
|
[formatting]
|
||||||
|
reorder_keys = false
|
||||||
|
|
||||||
|
[[rule]]
|
||||||
|
include = ["**/Cargo.toml"]
|
||||||
|
keys = ["dependencies"]
|
||||||
|
|
||||||
|
[rule.formatting]
|
||||||
|
reorder_keys = true
|
||||||
Loading…
Add table
Add a link
Reference in a new issue