diff --git a/examples/service_account/Cargo.toml b/examples/service_account/Cargo.toml new file mode 100644 index 000000000..c8cc4d849 --- /dev/null +++ b/examples/service_account/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "service_account" +version = "0.1.0" +authors = ["Lewin Bormann "] + +[dependencies] +base64 = "0.2" +yup-oauth2 = "0.6.4" +google-pubsub1 = { version = "0.1", path = "../../../google-apis-rs/gen/pubsub1" } +hyper = "0.9" diff --git a/examples/service_account/pubsub-auth.json b/examples/service_account/pubsub-auth.json new file mode 100644 index 000000000..8e0692115 --- /dev/null +++ b/examples/service_account/pubsub-auth.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "sanguine-rhythm-105020", + "private_key_id": "bbd866b207d9081b56d6cdce09782bcc5cc7b55b", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCgSvDJJ+r88GO9\nJgAserwS+T+BL7BKHjR9EWi6AaTkIMbfbR9juDa0hAtgEdW68Qmdo1ABLtjITzXM\nE2hR4Q00Q3QV+kZztfpXOyBcARzI0oUG33EY9BOsXU4fAhIMEjGj9wP2YUlPIEYt\n6CGarRCJoTykITvFL5ERtt/7yHKdcmC1XqdnHz1loJokoLixrWIQ9vHYFnlAGXN8\n4HB63GIdbF1gbrQ+yCbJTSkSf+Jf+MSVZ3bE4QNmwxj9pBpqkQuvR2c/uNHGHDzD\nKGnENsbkqgUnSc4Tv+0Im6QP1NjZytmlmhGkEyj2Bws/mbWGL8qjTQUdJoF/JSpd\nzjVDaDRpAgMBAAECggEAQafwkLSFEC327JcQIyfyuWLWnDfIYdOVdFUZLHdX7wlt\n7D5qSRI/dhlP/33Oqc/pPGSUdKeXrBSl2H1qkX85RVsLxaTKDsNPU0OosYbTH377\ndiE31hzDoiplwVW7xq39H0gim1ymBn8Vv+ABQ7X3vCgLCr+CF/av2S2q+RrVt59n\n+YXT+ucANHc33T5pV/AdvKXcfDvmzbXKZQ7orY3u1hvgjJC7uy7FKEMGnGbiVWN5\nMvSaUygHB+McSqxofWFqUxSmFhS3DNtQDH7Jf7j/s6IHaBFE4m63ursCITLOBv6Y\nEKT+Fr5WcDsbxUAI8E3Ol+BkjEko9mblGWCx2IWeMQKBgQDOxjlVKaFZn84SXvah\nLEYVgvUtyg3HyKbF9fCs9YN04nEoT+WtWAn7whBk/nBARJAmVAsqW0zVR3rQ6fCW\nBESLIAlNwVTQtnrUM9F0SNzpzRWUQkMNvIpORDO0ENR1QTKdpDLzjeW3+QEvUhWy\nv363U6m2tVyi3dzH0ou6UXf3vwKBgQDGc+p8O3rO8y8R+MwEdrQIkhemqYPDMHO0\n8D8D6E/roLrUPrfeKW/ZtaQAKg6nSthwRA0/QRVI3RP7MfHRcCka7tjZHWVd2ygh\n8/FRbb9DFuBwCRF2HFp8sFCfM0DJlQqHnmcq8cgmzrLQhMSIlxjYDcTVNUQ7LVuA\nQeMfMQmd1wKBgA1O4L3EcigMivzPs9vnt5MG3LTXvk9PcLxo+daH7rAFKvdHPScM\n9YeIk/mMDrl0MDrnSdiuC6HmedccV2mwWKEDrNWeTdEpDXq+4woy7lT16B+krCAk\nNFqZNSKvupzZV2KiAM2AInrWwebDx7W7cXJnJhMmVRH+aYlK2cdiGvCNAoGAKKGO\n/sSGzlxH5NwVmUxca+zNXtgSHCKNbhxKKpij1FBiMBZvOtMtDVv9KfNycJoIWmP0\nemDu5K4u6x08r27RW6xPRZSZLnAydVEmtU9M+4Vxij6xDoebly/aMd4ig6M6Nq4d\n3VMHcbqcLckhG/4jngijpitzvtH4i/fxcm9t0p0CgYEAukb0W9DYuoG/OHTKlUIv\nKr0bxLv6rRk5g7OoKD5D+QiWlXQ2RXeBlXPPkR4+N/WuxxQsJ/zlOi2iKM0lB+jG\nwwPCo/WtXm5WNi4gSVZIeOtIZS+e/2QeStc/WPgVhJrZjQ118/ORaeDoVo77lkEd\nvBj5QAMZDCn/3qeOpGJ1NJo=\n-----END PRIVATE KEY-----\n", + "client_email": "pubsub-1@sanguine-rhythm-105020.iam.gserviceaccount.com", + "client_id": "117880612788657019000", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/pubsub-1%40sanguine-rhythm-105020.iam.gserviceaccount.com" +} diff --git a/examples/service_account/src/main.rs b/examples/service_account/src/main.rs new file mode 100644 index 000000000..bba305e43 --- /dev/null +++ b/examples/service_account/src/main.rs @@ -0,0 +1,180 @@ +//! Demonstrates the use of service accounts and the Google Cloud Pubsub API. +//! +//! Run this binary as .../service_account pub 'your message' in order to publish messages, +//! and as .../service_account sub in order to subscribe to those messages. This will look like the +//! following: +//! +//! ``` +//! $ target/debug/service_account pub 'Hello oh wonderful world' & +//! $ target/debug/service_account sub +//! Published message #95491011619126 +//! message <95491011619126> 'Hello oh wonderful world' at 2016-09-21T20:04:47.040Z +//! Published message #95491011620879 +//! message <95491011620879> 'Hello oh wonderful world' at 2016-09-21T20:04:49.086Z +//! Published message #95491011622600 +//! message <95491011622600> 'Hello oh wonderful world' at 2016-09-21T20:04:51.132Z +//! Published message #95491011624393 +//! message <95491011624393> 'Hello oh wonderful world' at 2016-09-21T20:04:53.187Z +//! Published message #95491011626206 +//! message <95491011626206> 'Hello oh wonderful world' at 2016-09-21T20:04:55.233Z +//! +//! Copyright (c) 2016 Google, Inc. (Lewin Bormann ) +//! +extern crate base64; +extern crate yup_oauth2 as oauth; +extern crate google_pubsub1 as pubsub; +extern crate hyper; + +use std::env; +use std::time; +use std::thread; + +use pubsub::{Topic, Subscription}; + +// The prefixes are important! +const SUBSCRIPTION_NAME: &'static str = "projects/sanguine-rhythm-105020/subscriptions/rust_authd_sub_1"; +const TOPIC_NAME: &'static str = "projects/sanguine-rhythm-105020/topics/topic-01"; + +type PubsubMethods<'a> = pubsub::ProjectMethods<'a, + hyper::Client, + oauth::ServiceAccountAccess>; + +// Verifies that the topic TOPIC_NAME exists, or creates it. +fn check_or_create_topic(methods: &PubsubMethods) -> Topic { + let result = methods.topics_get(TOPIC_NAME).doit(); + + if result.is_err() { + println!("Assuming topic doesn't exist; creating topic"); + let topic = pubsub::Topic { name: Some(TOPIC_NAME.to_string()) }; + let result = methods.topics_create(topic, TOPIC_NAME).doit().unwrap(); + result.1 + } else { + result.unwrap().1 + } +} + +fn check_or_create_subscription(methods: &PubsubMethods) -> Subscription { + // check if subscription exists + let result = methods.subscriptions_get(SUBSCRIPTION_NAME).doit(); + + if result.is_err() { + println!("Assuming subscription doesn't exist; creating subscription"); + let sub = pubsub::Subscription { + topic: Some(TOPIC_NAME.to_string()), + ack_deadline_seconds: Some(30), + push_config: None, + name: Some(SUBSCRIPTION_NAME.to_string()), + }; + let (_resp, sub) = methods.subscriptions_create(sub, SUBSCRIPTION_NAME).doit().unwrap(); + + sub + } else { + result.unwrap().1 + } +} + +fn ack_message(methods: &PubsubMethods, id: String) { + let request = pubsub::AcknowledgeRequest { ack_ids: Some(vec![id]) }; + let result = methods.subscriptions_acknowledge(request, SUBSCRIPTION_NAME).doit(); + + match result { + Err(e) => { + println!("Ack error: {:?}", e); + } + Ok(_) => (), + } +} + +// Wait for new messages. Print and ack any new messages. +fn subscribe_wait(methods: &PubsubMethods) { + check_or_create_subscription(&methods); + + let request = pubsub::PullRequest { + return_immediately: Some(false), + max_messages: Some(1), + }; + + + loop { + let result = methods.subscriptions_pull(request.clone(), SUBSCRIPTION_NAME).doit(); + + match result { + Err(e) => { + println!("Pull error: {}", e); + } + Ok((_response, pullresponse)) => { + for msg in pullresponse.received_messages.unwrap_or(Vec::new()) { + let ack_id = msg.ack_id.unwrap_or(String::new()); + let message = msg.message.unwrap_or(Default::default()); + println!("message <{}> '{}' at {}", + message.message_id.unwrap_or(String::new()), + String::from_utf8(base64::decode(&message.data + .unwrap_or(String::new())) + .unwrap()) + .unwrap(), + message.publish_time.unwrap_or(String::new())); + + if ack_id != "" { + ack_message(methods, ack_id); + } + } + } + } + } +} + +// Publish some message every 2 seconds. +fn publish_stuff(methods: &PubsubMethods, message: &str) { + check_or_create_topic(&methods); + + let message = pubsub::PubsubMessage { + // Base64 encoded! + data: Some(base64::encode(message.as_bytes())), + ..Default::default() + }; + let request = pubsub::PublishRequest { messages: Some(vec![message]) }; + + + loop { + let result = methods.topics_publish(request.clone(), TOPIC_NAME).doit(); + + match result { + Err(e) => { + println!("Publish error: {}", e); + } + Ok((_response, pubresponse)) => { + for msg in pubresponse.message_ids.unwrap_or(Vec::new()) { + println!("Published message #{}", msg); + } + } + } + + thread::sleep(time::Duration::new(2, 0)); + } +} + +// If called as '.../service_account pub', act as publisher; if called as '.../service_account +// sub', act as subscriber. +fn main() { + let client_secret = oauth::service_account_key_from_file(&"pubsub-auth.json".to_string()) + .unwrap(); + let mut access = oauth::ServiceAccountAccess::new(client_secret, hyper::Client::new()); + + use oauth::GetToken; + println!("{:?}", + access.token(&vec!["https://www.googleapis.com/auth/pubsub"]).unwrap()); + + let hub = pubsub::Pubsub::new(hyper::Client::new(), access); + let methods = hub.projects(); + + let mode = env::args().nth(1).unwrap_or(String::new()); + + if mode == "pub" { + let message = env::args().nth(2).unwrap_or("Hello World!".to_string()); + publish_stuff(&methods, &message); + } else if mode == "sub" { + subscribe_wait(&methods); + } else { + println!("Please use either of 'pub' or 'sub' as first argument to this binary!"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 1c8c7bac7..5832b821b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,12 @@ //! as means to adhere to the protocol, and remain resilient to all kinds of errors //! that can occour on the way. //! +//! # Service account "flow" +//! When using service account credentials, no user interaction is required. The access token +//! can be obtained automatically using the private key of the client (which you can download +//! from the API provider). See `examples/service_account/` for an example on how to use service +//! account credentials. +//! //! # Installed Flow Usage //! The `InstalledFlow` involves showing a URL to the user (or opening it in a browser) //! and then either prompting the user to enter a displayed code, or make the authorizing