// Copyright (c) 2016-2017 Chef Software Inc. and/or applicable contributors // // 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. #![cfg_attr(feature="clippy", feature(plugin))] #![cfg_attr(feature="clippy", plugin(clippy))] #[macro_use] extern crate clap; extern crate hab; extern crate habitat_core as hcore; extern crate habitat_common as common; extern crate handlebars; extern crate rusoto_core; extern crate rusoto_ecr; extern crate rusoto_credential as aws_creds; #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; #[macro_use] extern crate serde_json; extern crate tempdir; extern crate base64; extern crate url; extern crate failure; #[macro_use] extern crate failure_derive; mod build; pub mod cli; mod docker; mod error; mod fs; pub mod rootfs; mod util; use common::ui::UI; use aws_creds::StaticProvider; use rusoto_core::Region; use rusoto_core::request::*; use rusoto_ecr::{Ecr, EcrClient, GetAuthorizationTokenRequest}; pub use cli::Cli; pub use build::BuildSpec; pub use docker::{DockerImage, DockerBuildRoot}; pub use error::{Error, Result}; /// The version of this library and program when built. pub const VERSION: &'static str = include_str!(concat!(env!("OUT_DIR"), "/VERSION")); /// The Habitat Package Identifier string for a Busybox package. const BUSYBOX_IDENT: &'static str = "core/busybox-static"; /// The Habitat Package Identifier string for SSL certificate authorities (CA) certificates package. const CACERTS_IDENT: &'static str = "core/cacerts"; /// An image naming policy. /// /// This is a value struct which captures the naming and tagging intentions for an image. #[derive(Debug)] pub struct Naming<'a> { /// An optional custom image name which would override a computed default value. pub custom_image_name: Option<&'a str>, /// Whether or not to tag the image with a latest value. pub latest_tag: bool, /// Whether or not to tag the image with a value containing a version from a Package /// Identifier. pub version_tag: bool, /// Whether or not to tag the image with a value containing a version and release from a /// Package Identifier. pub version_release_tag: bool, /// An optional custom tag value for the image. pub custom_tag: Option<&'a str>, /// A URL to a custom Docker registry to publish to. This will be used as part of every tag /// before pushing. pub registry_url: Option<&'a str>, /// The type of registry we're publishing to. Ex: Amazon, Docker, Google, Azure. pub registry_type: &'a str, } impl<'a> Naming<'a> { /// Creates a `Naming` from cli arguments. pub fn new_from_cli_matches(m: &'a clap::ArgMatches) -> Self { let registry_type = m.value_of("REGISTRY_TYPE").unwrap_or("docker"); let registry_url = m.value_of("REGISTRY_URL"); Naming { custom_image_name: m.value_of("IMAGE_NAME"), latest_tag: !m.is_present("NO_TAG_LATEST"), version_tag: !m.is_present("NO_TAG_VERSION"), version_release_tag: !m.is_present("NO_TAG_VERSION_RELEASE"), custom_tag: m.value_of("TAG_CUSTOM"), registry_url: registry_url, registry_type: registry_type, } } } /// A credentials username and password pair. /// /// This is a value struct which references username and password values. #[derive(Debug)] pub struct Credentials { pub username: String, pub password: String, } impl Credentials { pub fn new(registry_type: &str, username: &str, password: &str) -> Result<Self> { match registry_type { "amazon" => { // The username and password should be valid IAM credentials let provider = StaticProvider::new_minimal(username.to_string(), password.to_string()); // TODO TED: Make the region configurable let client = EcrClient::new(default_tls_client().unwrap(), provider, Region::UsWest2); let auth_token_req = GetAuthorizationTokenRequest { registry_ids: None }; let token = match client.get_authorization_token(&auth_token_req) { Ok(resp) => { match resp.authorization_data { Some(auth_data) => auth_data[0].clone().authorization_token.unwrap(), None => return Err(Error::NoECRTokensReturned.into()), } } Err(e) => return Err(Error::TokenFetchFailed(e).into()), }; let creds: Vec<String> = match base64::decode(&token) { Ok(decoded_token) => { match String::from_utf8(decoded_token) { Ok(dts) => dts.split(':').map(String::from).collect(), Err(err) => return Err(Error::InvalidToken(err).into()), } } Err(err) => return Err(Error::Base64DecodeError(err).into()), }; Ok(Credentials { username: creds[0].to_string(), password: creds[1].to_string(), }) } _ => { Ok(Credentials { username: username.to_string(), password: password.to_string(), }) } } } } /// Exports a Docker image to a Docker engine from a build specification and naming policy. /// /// # Errors /// /// * If a generic and temporary build root directory cannot be created containing a root /// file system /// * If additional Docker-related files cannot be created in the root file system /// * If building the Docker image fails /// * If destroying the temporary build root directory fails pub fn export(ui: &mut UI, build_spec: BuildSpec, naming: &Naming) -> Result<DockerImage> { ui.begin(format!( "Building a runnable Docker image with: {}", build_spec.idents_or_archives.join(", ") ))?; let build_root = DockerBuildRoot::from_build_root(build_spec.create(ui)?, ui)?; let image = build_root.export(ui, naming)?; build_root.destroy(ui)?; ui.end(format!( "Docker image '{}' created with tags: {}", image.name(), image.tags().join(", ") ))?; Ok(image) }