[omaha-client] Add omaha_client_ctl.

A commandline tool that talks to omaha client FIDL interface.

Bug: PKG-485
Test: fx shell omaha_client_ctl
Test: fx shell omaha_client_ctl get-channel
Test: fx shell omaha_client_ctl set-channel dev-channel
Test: fx shell omaha_client_ctl get-state
Test: fx shell omaha_client_ctl check-now
Test: fx shell omaha_client_ctl check-now --monitor
Test: fx shell omaha_client_ctl monitor
Change-Id: I7b9a6b4f6cbc91743176022c490376424050ca01
diff --git a/garnet/bin/omaha_client_ctl/BUILD.gn b/garnet/bin/omaha_client_ctl/BUILD.gn
new file mode 100644
index 0000000..25ab433
--- /dev/null
+++ b/garnet/bin/omaha_client_ctl/BUILD.gn
@@ -0,0 +1,41 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("omaha_client_ctl_bin") {
+  name = "omaha_client_ctl_bin"
+  edition = "2018"
+  deps = [
+    "//garnet/bin/omaha_client:fuchsia.omaha.client-rustc",
+    "//garnet/bin/omaha_client:fuchsia.update-rustc",
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-component",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//third_party/rust_crates:failure",
+    "//third_party/rust_crates:futures-preview",
+    "//third_party/rust_crates:structopt",
+  ]
+}
+
+package("omaha_client_ctl") {
+  deps = [
+    ":omaha_client_ctl_bin",
+  ]
+  binaries = [
+    {
+      name = "rust_crates/omaha_client_ctl_bin"
+      dest = "omaha_client_ctl"
+      shell = true
+    },
+  ]
+  meta = [
+    {
+      path = rebase_path("meta/omaha_client_ctl.cmx")
+      dest = "omaha_client_ctl.cmx"
+    },
+  ]
+}
diff --git a/garnet/bin/omaha_client_ctl/meta/omaha_client_ctl.cmx b/garnet/bin/omaha_client_ctl/meta/omaha_client_ctl.cmx
new file mode 100644
index 0000000..0fb89fe
--- /dev/null
+++ b/garnet/bin/omaha_client_ctl/meta/omaha_client_ctl.cmx
@@ -0,0 +1,10 @@
+{
+    "program": {
+        "binary": "bin/omaha_client_ctl"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.sys.Launcher"
+        ]
+    }
+}
diff --git a/garnet/bin/omaha_client_ctl/src/main.rs b/garnet/bin/omaha_client_ctl/src/main.rs
new file mode 100644
index 0000000..c56c2a2
--- /dev/null
+++ b/garnet/bin/omaha_client_ctl/src/main.rs
@@ -0,0 +1,144 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![feature(async_await, await_macro, futures_api)]
+
+use failure::{Error, ResultExt};
+use fidl_fuchsia_omaha_client::OmahaClientConfigurationMarker;
+use fidl_fuchsia_update::{
+    Initiator, ManagerMarker, MonitorEvent, MonitorMarker, MonitorProxy, Options, State,
+};
+use fuchsia_async as fasync;
+use fuchsia_component::client::{launch, launcher};
+use fuchsia_zircon as zx;
+use futures::prelude::*;
+use structopt::StructOpt;
+
+fn print_state(state: State) {
+    if let Some(state) = state.state {
+        println!("State: {:?}", state);
+    }
+    if let Some(version) = state.version_available {
+        println!("Version available: {}", version);
+    }
+}
+
+async fn monitor_state(monitor: MonitorProxy) -> Result<(), Error> {
+    let mut stream = monitor.take_event_stream();
+    while let Some(event) = await!(stream.try_next())? {
+        match event {
+            MonitorEvent::OnState { state } => {
+                print_state(state);
+            }
+        }
+    }
+    Ok(())
+}
+
+#[fasync::run_singlethreaded]
+async fn main() -> Result<(), Error> {
+    #[derive(Debug, StructOpt)]
+    #[structopt(name = "omaha_client_ctl")]
+    struct Opt {
+        #[structopt(
+            long = "server",
+            help = "URL of omaha client server",
+            default_value = "fuchsia-pkg://fuchsia.com/omaha_client#meta/omaha_client_service.cmx"
+        )]
+        server_url: String,
+
+        #[structopt(subcommand)]
+        cmd: Command,
+    }
+    #[derive(Debug, StructOpt)]
+    #[structopt(rename_all = "kebab-case")]
+    enum Command {
+        // fuchsia.omaha.client OmahaClientConfiguration protocol:
+        GetChannel,
+        SetChannel {
+            channel: String,
+
+            #[structopt(long = "no-factory-reset")]
+            // Can't change default value for bool, it always defaults to false.
+            no_factory_reset: bool,
+        },
+
+        // fuchsia.update Manager protocol:
+        GetState,
+        CheckNow {
+            /// The update check was initiated by a service, in the background.
+            #[structopt(long = "service-initiated")]
+            service_initiated: bool,
+
+            /// Monitor for state update.
+            #[structopt(long)]
+            monitor: bool,
+        },
+        Monitor,
+    }
+
+    // Launch the server and connect to the omaha client service.
+    let Opt { server_url, cmd } = Opt::from_args();
+    let launcher = launcher().context("Failed to open launcher service")?;
+    let app =
+        launch(&launcher, server_url, None).context("Failed to launch omaha client service")?;
+    match cmd {
+        Command::GetChannel | Command::SetChannel { .. } => {
+            let omaha_client = app
+                .connect_to_service::<OmahaClientConfigurationMarker>()
+                .context("Failed to connect to omaha client configuration service")?;
+
+            match cmd {
+                Command::GetChannel => {
+                    let channel = await!(omaha_client.get_channel())?;
+                    println!("channel: {}", channel);
+                }
+                Command::SetChannel { channel, no_factory_reset } => {
+                    let status = await!(omaha_client.set_channel(&channel, !no_factory_reset))?;
+                    zx::Status::ok(status)?;
+                }
+                _ => {}
+            }
+        }
+        Command::GetState | Command::CheckNow { .. } | Command::Monitor => {
+            let omaha_client = app
+                .connect_to_service::<ManagerMarker>()
+                .context("Failed to connect to omaha client manager service")?;
+
+            match cmd {
+                Command::GetState => {
+                    let state = await!(omaha_client.get_state())?;
+                    print_state(state);
+                }
+                Command::CheckNow { service_initiated, monitor } => {
+                    let options = Options {
+                        initiator: Some(if service_initiated {
+                            Initiator::Service
+                        } else {
+                            Initiator::User
+                        }),
+                    };
+                    if monitor {
+                        let (client_proxy, server_end) =
+                            fidl::endpoints::create_proxy::<MonitorMarker>()?;
+                        let result = await!(omaha_client.check_now(options, Some(server_end)))?;
+                        println!("Check started result: {:?}", result);
+                        await!(monitor_state(client_proxy))?;
+                    } else {
+                        let result = await!(omaha_client.check_now(options, None))?;
+                        println!("Check started result: {:?}", result);
+                    }
+                }
+                Command::Monitor => {
+                    let (client_proxy, server_end) =
+                        fidl::endpoints::create_proxy::<MonitorMarker>()?;
+                    omaha_client.add_monitor(server_end)?;
+                    await!(monitor_state(client_proxy))?;
+                }
+                _ => {}
+            }
+        }
+    }
+    Ok(())
+}
diff --git a/garnet/packages/prod/BUILD.gn b/garnet/packages/prod/BUILD.gn
index 399ed02..79c62788 100644
--- a/garnet/packages/prod/BUILD.gn
+++ b/garnet/packages/prod/BUILD.gn
@@ -1118,6 +1118,7 @@
   testonly = true
   public_deps = [
     "//garnet/bin/omaha_client",
+    "//garnet/bin/omaha_client_ctl",
   ]
 }