-
-
Notifications
You must be signed in to change notification settings - Fork 325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add port forward #446
Merged
Merged
Add port forward #446
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
fbf8d94
Add Portforward support
kazk 4a96785
Pod port-forward hyper HTTP request example
partiallyordered 84a154c
Use `await_condition` and reduce `unwrap` in examples
kazk 36b0bdd
Change `Portforwarder::ports()` to return `&mut [Port]`
kazk d0da0a8
Improve docs
kazk bc05653
Avoid panics and split into smaller functions
kazk 5582177
Clarify the purpose of the task
kazk bc2984a
Fix unused import
kazk c3cb3fe
Add an example to bind a local port
kazk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use futures::StreamExt; | ||
use k8s_openapi::api::core::v1::Pod; | ||
|
||
use kube::{ | ||
api::{Api, DeleteParams, PostParams}, | ||
runtime::wait::{await_condition, conditions::is_pod_running}, | ||
Client, ResourceExt, | ||
}; | ||
|
||
use tokio::io::AsyncWriteExt; | ||
|
||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
std::env::set_var("RUST_LOG", "info,kube=debug"); | ||
env_logger::init(); | ||
let client = Client::try_default().await?; | ||
let namespace = std::env::var("NAMESPACE").unwrap_or_else(|_| "default".into()); | ||
|
||
let p: Pod = serde_json::from_value(serde_json::json!({ | ||
"apiVersion": "v1", | ||
"kind": "Pod", | ||
"metadata": { "name": "example" }, | ||
"spec": { | ||
"containers": [{ | ||
"name": "nginx", | ||
"image": "nginx", | ||
}], | ||
} | ||
}))?; | ||
|
||
let pods: Api<Pod> = Api::namespaced(client, &namespace); | ||
// Stop on error including a pod already exists or is still being deleted. | ||
pods.create(&PostParams::default(), &p).await?; | ||
|
||
// Wait until the pod is running, otherwise we get 500 error. | ||
let running = await_condition(pods.clone(), "example", is_pod_running()); | ||
let _ = tokio::time::timeout(std::time::Duration::from_secs(15), running).await?; | ||
|
||
let mut pf = pods.portforward("example", &[80]).await?; | ||
let ports = pf.ports(); | ||
let mut port = ports[0].stream().unwrap(); | ||
port.write_all(b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\nAccept: */*\r\n\r\n") | ||
.await?; | ||
let mut rstream = tokio_util::io::ReaderStream::new(port); | ||
if let Some(res) = rstream.next().await { | ||
match res { | ||
Ok(bytes) => { | ||
let response = std::str::from_utf8(&bytes[..]).unwrap(); | ||
println!("{}", response); | ||
assert!(response.contains("Welcome to nginx!")); | ||
} | ||
Err(err) => eprintln!("{:?}", err), | ||
} | ||
} | ||
|
||
// Delete it | ||
println!("deleting"); | ||
pods.delete("example", &DeleteParams::default()) | ||
.await? | ||
.map_left(|pdel| { | ||
assert_eq!(pdel.name(), "example"); | ||
}); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// Example to listen on port 8080 locally, forwarding to port 80 in the example pod. | ||
// Similar to `kubectl port-forward pod/example 8080:80`. | ||
use std::{convert::Infallible, net::SocketAddr, sync::Arc}; | ||
|
||
use futures::FutureExt; | ||
use hyper::{ | ||
service::{make_service_fn, service_fn}, | ||
Body, Request, Response, Server, | ||
}; | ||
use tokio::sync::Mutex; | ||
use tower::ServiceExt; | ||
|
||
use k8s_openapi::api::core::v1::Pod; | ||
use kube::{ | ||
api::{Api, DeleteParams, PostParams}, | ||
runtime::wait::{await_condition, conditions::is_pod_running}, | ||
Client, ResourceExt, | ||
}; | ||
|
||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
std::env::set_var("RUST_LOG", "info,kube=debug"); | ||
env_logger::init(); | ||
let client = Client::try_default().await?; | ||
let namespace = std::env::var("NAMESPACE").unwrap_or_else(|_| "default".into()); | ||
|
||
let p: Pod = serde_json::from_value(serde_json::json!({ | ||
"apiVersion": "v1", | ||
"kind": "Pod", | ||
"metadata": { "name": "example" }, | ||
"spec": { | ||
"containers": [{ | ||
"name": "nginx", | ||
"image": "nginx", | ||
}], | ||
} | ||
}))?; | ||
|
||
let pods: Api<Pod> = Api::namespaced(client, &namespace); | ||
// Stop on error including a pod already exists or is still being deleted. | ||
pods.create(&PostParams::default(), &p).await?; | ||
|
||
// Wait until the pod is running, otherwise we get 500 error. | ||
let running = await_condition(pods.clone(), "example", is_pod_running()); | ||
let _ = tokio::time::timeout(std::time::Duration::from_secs(30), running).await?; | ||
|
||
// Get `Portforwarder` that handles the WebSocket connection. | ||
// There's no need to spawn a task to drive this, but it can be awaited to be notified on error. | ||
let mut forwarder = pods.portforward("example", &[80]).await?; | ||
let port = forwarder.ports()[0].stream().unwrap(); | ||
|
||
// let hyper drive the HTTP state in our DuplexStream via a task | ||
let (sender, connection) = hyper::client::conn::handshake(port).await?; | ||
tokio::spawn(async move { | ||
if let Err(e) = connection.await { | ||
log::error!("error in connection: {}", e); | ||
} | ||
}); | ||
// The following task is only used to show any error from the forwarder. | ||
// This example can be stopped with Ctrl-C if anything happens. | ||
tokio::spawn(async move { | ||
if let Err(e) = forwarder.await { | ||
log::error!("forwarder errored: {}", e); | ||
} | ||
}); | ||
|
||
// Shared `SendRequest<Body>` to relay the request. | ||
let context = Arc::new(Mutex::new(sender)); | ||
let make_service = make_service_fn(move |_conn| { | ||
let context = context.clone(); | ||
let service = service_fn(move |req| handle(context.clone(), req)); | ||
async move { Ok::<_, Infallible>(service) } | ||
}); | ||
|
||
let (tx, rx) = tokio::sync::oneshot::channel::<()>(); | ||
let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); | ||
let server = Server::bind(&addr) | ||
.serve(make_service) | ||
.with_graceful_shutdown(async { | ||
rx.await.ok(); | ||
}); | ||
println!("Forwarding http://{} to port 80 in the pod", addr); | ||
println!("Try opening http://{0} in a browser, or `curl http://{0}`", addr); | ||
println!("Use Ctrl-C to stop the server and delete the pod"); | ||
// Stop the server and delete the pod on Ctrl-C. | ||
tokio::spawn(async move { | ||
tokio::signal::ctrl_c().map(|_| ()).await; | ||
log::info!("stopping the server"); | ||
let _ = tx.send(()); | ||
}); | ||
if let Err(e) = server.await { | ||
log::error!("server error: {}", e); | ||
} | ||
|
||
log::info!("deleting the pod"); | ||
pods.delete("example", &DeleteParams::default()) | ||
.await? | ||
.map_left(|pdel| { | ||
assert_eq!(pdel.name(), "example"); | ||
}); | ||
|
||
Ok(()) | ||
} | ||
|
||
// Simply forwards the request to the port through the shared `SendRequest<Body>`. | ||
async fn handle( | ||
context: Arc<Mutex<hyper::client::conn::SendRequest<hyper::Body>>>, | ||
req: Request<Body>, | ||
) -> Result<Response<Body>, Infallible> { | ||
let mut sender = context.lock().await; | ||
let response = sender.ready().await.unwrap().send_request(req).await.unwrap(); | ||
Ok(response) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use k8s_openapi::api::core::v1::Pod; | ||
|
||
use kube::{ | ||
api::{Api, DeleteParams, PostParams}, | ||
runtime::wait::{await_condition, conditions::is_pod_running}, | ||
Client, ResourceExt, | ||
}; | ||
|
||
use hyper::{body, Body, Request}; | ||
|
||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
std::env::set_var("RUST_LOG", "info,kube=debug"); | ||
env_logger::init(); | ||
let client = Client::try_default().await?; | ||
let namespace = std::env::var("NAMESPACE").unwrap_or_else(|_| "default".into()); | ||
|
||
let p: Pod = serde_json::from_value(serde_json::json!({ | ||
"apiVersion": "v1", | ||
"kind": "Pod", | ||
"metadata": { "name": "example" }, | ||
"spec": { | ||
"containers": [{ | ||
"name": "nginx", | ||
"image": "nginx", | ||
}], | ||
} | ||
}))?; | ||
|
||
let pods: Api<Pod> = Api::namespaced(client, &namespace); | ||
// Stop on error including a pod already exists or is still being deleted. | ||
pods.create(&PostParams::default(), &p).await?; | ||
|
||
// Wait until the pod is running, otherwise we get 500 error. | ||
let running = await_condition(pods.clone(), "example", is_pod_running()); | ||
let _ = tokio::time::timeout(std::time::Duration::from_secs(15), running).await?; | ||
|
||
let mut pf = pods.portforward("example", &[80]).await?; | ||
let ports = pf.ports(); | ||
let port = ports[0].stream().unwrap(); | ||
|
||
// let hyper drive the HTTP state in our DuplexStream via a task | ||
let (mut sender, connection) = hyper::client::conn::handshake(port).await?; | ||
tokio::spawn(async move { | ||
if let Err(e) = connection.await { | ||
eprintln!("Error in connection: {}", e); | ||
} | ||
}); | ||
|
||
let http_req = Request::builder() | ||
.uri("/") | ||
.header("Connection", "close") | ||
.header("Host", "127.0.0.1") | ||
.method("GET") | ||
.body(Body::from("")) | ||
.unwrap(); | ||
|
||
let (parts, body) = sender.send_request(http_req).await?.into_parts(); | ||
assert!(parts.status == 200); | ||
|
||
let body_bytes = body::to_bytes(body).await?; | ||
let body_str = std::str::from_utf8(&body_bytes)?; | ||
assert!(body_str.contains("Welcome to nginx!")); | ||
|
||
// Delete it | ||
println!("deleting"); | ||
pods.delete("example", &DeleteParams::default()) | ||
.await? | ||
.map_left(|pdel| { | ||
assert_eq!(pdel.name(), "example"); | ||
}); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool quick use of tower!