1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/*
   Copyright The containerd Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

//! Helper library to implement custom shim loggers for containerd.

use std::env;
use std::fmt;
use std::fs;
use std::os::unix::io::FromRawFd;
use std::process;

/// Logging binary configuration received from containerd.
pub struct Config {
    pub id: String,
    pub namespace: String,
    pub stdout: fs::File,
    pub stderr: fs::File,
}

impl Config {
    /// Creates a new configuration object. This will pull environment provided by containerd to fill
    /// up [Config] structure.
    ///
    /// `new` will panic if the environment is incorrect (not likely to happen if used properly).
    fn new() -> Config {
        let id = match env::var("CONTAINER_ID") {
            Ok(id) => id,
            Err(_) => handle_err("CONTAINER_ID env not found"),
        };

        let namespace = match env::var("CONTAINER_NAMESPACE") {
            Ok(ns) => ns,
            Err(_) => handle_err("CONTAINER_NAMESPACE env not found"),
        };

        let stdout = unsafe { fs::File::from_raw_fd(3) };
        let stderr = unsafe { fs::File::from_raw_fd(4) };

        Config {
            id,
            namespace,
            stdout,
            stderr,
        }
    }
}

/// Tiny wrapper around containerd's wait signal file.
/// See https://github.com/containerd/containerd/blob/dbef1d56d7ebc05bc4553d72c419ed5ce025b05d/runtime/v2/logging/logging_unix.go#L44
struct Ready(fs::File);

impl Ready {
    fn new() -> Ready {
        Ready(unsafe { fs::File::from_raw_fd(5) })
    }

    /// Signal that we are ready and setup for the container to be started.
    fn signal(self) {
        drop(self.0)
    }
}

/// Driver is a trait to be implemented by v2 logging binaries.
/// This trait is Rusty alternative to Go's `LoggerFunc`.
pub trait Driver: Sized {
    /// The error type to be returned from driver routines if something goes wrong.
    type Error: fmt::Debug;

    /// Create a new binary logger implementation from the provided `Config`.
    /// Once returned, the crate will signal that we're ready and
    /// setup for the container to be started
    fn new(config: Config) -> Result<Self, Self::Error>;

    /// Run the logging driver.
    /// The process will shutdown once exited.
    fn run(self) -> Result<(), Self::Error>;
}

/// Entry point to run the logging driver.
/// Typically `run` must be called from the `main` function to launch the driver.
pub fn run<D: Driver>() {
    let config = Config::new();
    let ready = Ready::new();

    // Initialize log driver
    let logger = match D::new(config) {
        Ok(driver) => driver,
        Err(err) => handle_err(err),
    };

    // Signal ready to pump log data
    ready.signal();

    // Run and block until exit
    if let Err(err) = logger.run() {
        handle_err(err)
    } else {
        process::exit(0);
    }
}

#[inline]
fn handle_err(err: impl fmt::Debug) -> ! {
    eprintln!("{:?}", err);
    process::exit(1);
}