dockerfile-0.2.1/.gitignore010064400007650000024000000000571340314345400140330ustar0000000000000000# Directory Ignores # target # File Ignores # dockerfile-0.2.1/.travis.yml010064400007650000024000000003031340314465100141460ustar0000000000000000sudo: required language: rust rust: - stable services: - docker before_install: - rustup install nightly script: - cargo test --all-targets - cargo +nightly test --doc --all-features dockerfile-0.2.1/Cargo.toml.orig010064400007650000024000000013051340351004200147160ustar0000000000000000[package] name = "dockerfile" description = "A Rust library for dynamically generating Dockerfiles." version = "0.2.1" authors = ["Anthony Dodd "] categories = ["api-bindings", "config", "data-structures", "development-tools"] documentation = "https://docs.rs/dockerfile" homepage = "https://github.com/thedodd/dockerfile" keywords = ["docker", "dockerfile"] license = "Apache-2.0" readme = "README.md" repository = "https://github.com/thedodd/dockerfile" edition = "2018" [dependencies] [features] docinclude = [] # Used only for activating `doc(include="...")` on nightly. [package.metadata.docs.rs] features = ["docinclude"] # Activate `docinclude` during docs.rs build. dockerfile-0.2.1/Cargo.toml0000644000000021440000000000000112010ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "dockerfile" version = "0.2.1" authors = ["Anthony Dodd "] description = "A Rust library for dynamically generating Dockerfiles." homepage = "https://github.com/thedodd/dockerfile" documentation = "https://docs.rs/dockerfile" readme = "README.md" keywords = ["docker", "dockerfile"] categories = ["api-bindings", "config", "data-structures", "development-tools"] license = "Apache-2.0" repository = "https://github.com/thedodd/dockerfile" [package.metadata.docs.rs] features = ["docinclude"] [dependencies] [features] docinclude = [] dockerfile-0.2.1/CHANGELOG.md010064400007650000024000000003061340351002400136400ustar0000000000000000changelog ========= ## 0.2 Support for all Dockerfile instructions have been added. ### 0.2.1 An update to the crate's docs. ## 0.1 The initial release. ### 0.1.1 An update to the crate's docs. dockerfile-0.2.1/CONTRIBUTING.md010064400007650000024000000007301340351025000142620ustar0000000000000000contributing ============ ### publishing releases - Ensure the README has been updated to reflect accurate examples and installation version. - Ensure the Cargo.toml version has been updated. - Ensure docs are updated and rendering as needed. ### development Running all tests for this system is pretty straightforward. - cargo test --all-targets - cargo +nightly test --doc --all-features To visually check the built docs: `cargo +nightly doc --all-features --open`. dockerfile-0.2.1/LICENSE010064400007650000024000000010601340314317000130360ustar0000000000000000Copyright 2017 Anthony Josiah Dodd 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. dockerfile-0.2.1/README.md010064400007650000024000000050021340350765600133250ustar0000000000000000dockerfile ========== [![Build Status](https://travis-ci.org/thedodd/dockerfile.svg?branch=master)](https://travis-ci.org/thedodd/dockerfile) [![Crates.io](https://img.shields.io/crates/v/dockerfile.svg)](https://crates.io/crates/dockerfile) [![docs.rs](https://docs.rs/dockerfile/badge.svg)](https://docs.rs/dockerfile) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) ![Crates.io](https://img.shields.io/crates/d/dockerfile.svg) ![Crates.io](https://img.shields.io/crates/dv/dockerfile.svg) A Rust library for dynamically generating Dockerfiles. The use case this crate was originally built for was to build Docker images from a worker service running in Kubernetes for client workloads. This is definitely not the only pattern that is supported. The generated Dockerfiles could be persisted somewhere or discarded immediately after use. The containers generated are standard containers, built according to the Dockerfiles you generated. All of the Dockerfile instructions are supported in raw form as of 2018.12.09. There is an issue open to add more structured and type-safe interfaces for the instructions which need it. ### get started First you will need to add this to your `Cargo.toml` dependencies. ```toml dockerfile = "0.2" ``` Now you can start building Dockerfiles. ```rust use dockerfile::{ Dockerfile, Arg, Copy, Cmd, }; fn main() { // Build up a new Dockerfile. let dockerfile = Dockerfile::base("rust:${RUST_VERSION}-slim") .push_initial_arg(Arg::new("RUST_VERSION=1.31")) .push(Copy::new("/static ./static")) .push(Cmd::new("echo 'Hello. Goodbye.'")) .finish(); // Write it out as a string. let output = dockerfile.to_string(); assert_eq!(output, r##"ARG RUST_VERSION=1.31 FROM rust:${RUST_VERSION}-slim COPY /static ./static CMD echo 'Hello. Goodbye.' "##) } ``` ### development I would like to have this crate offer a type-safe interface for constructing the various Dockerfile instructions. This will help reduce bugs which could only be found once you actually attempt to invoke the docker build. I would like to experiment with adding constructors for the various forms of instructions; EG, offer a constructor for `CMD` which takes an `impl Iterator>` for building the form `CMD ["arg0", "arg1"]` &c. dockerfile-0.2.1/src/builder.rs010064400007650000024000000074541340313452100146310ustar0000000000000000use std::{ borrow::Cow, fmt, }; use crate::{ instructions::{ Arg, Directive, From, Instruction, }, }; /// A Dockerfile represented in code. /// /// A Dockerfile, conceptually, is a series of instructions. In code, that is exactly how they are /// represented here. A wrapper around a `Vec` with a few convenience methods and /// such. pub struct Dockerfile(Vec); impl fmt::Display for Dockerfile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0.iter().fold(String::new(), |acc, elem| acc + &elem.to_string())) } } impl Dockerfile { /// Start building a new Dockerfile from the specified base image. /// /// Only requirement is the initial `FROM` instruction. Call `.finish()` when complete. pub fn base>>(from: T) -> DockerfileBuilder { DockerfileBuilder{ initial_directives: None, initial_args: None, from: From::new(from), instructions: None, } } } /// A Dockerfile builder. pub struct DockerfileBuilder { /// Any parser directives. initial_directives: Option>, /// Any initial `ARG`s which are to appear before the initial `FROM` instruction. initial_args: Option>, /// The new Dockerfile's initial `FROM` instruction. from: From, /// Any additional instructions part of the Dockerfile. instructions: Option>, } impl DockerfileBuilder { /// Push a new initial directive to this Dockerfile instance. pub fn push_initial_directive(mut self, directive: Directive) -> Self { if let Some(ref mut directives) = self.initial_directives { directives.push(directive); } else { self.initial_directives = Some(vec![directive]); } self } /// Push a new initial arg to this Dockerfile instance. pub fn push_initial_arg(mut self, arg: Arg) -> Self { if let Some(ref mut args) = self.initial_args { args.push(arg); } else { self.initial_args = Some(vec![arg]); } self } /// Push a new instruction into this Dockerfile instance. pub fn push>(mut self, instruction: I) -> Self { if let Some(ref mut instructions) = self.instructions { instructions.push(instruction.into()); } else { self.instructions = Some(vec![instruction.into()]); } self } /// Append a vector of instructions to this Dockerfile instance. pub fn append>(mut self, new: Vec) -> Self { let mut new = new.into_iter().map(Into::into).collect(); if let Some(ref mut instructions) = self.instructions { instructions.append(&mut new); } else { self.instructions = Some(new); } self } /// Generate the output Dockerfile as a string. pub fn finish(self) -> Dockerfile { // Add directives to the Dockerfile. let mut all_instructions: Vec = vec![]; if let Some(directives) = self.initial_directives { all_instructions.extend(directives.into_iter().map(|inst| Instruction::Directive(inst))); } // Add initial args to the Dockerfile. if let Some(args) = self.initial_args { all_instructions.extend(args.into_iter().map(|inst| Instruction::Arg(inst))); } // Add from instruction to Dockerfile. all_instructions.push(Instruction::From(self.from)); // Append any other instructions in serial order. if let Some(instructions) = self.instructions { all_instructions.extend(instructions.into_iter()); } Dockerfile(all_instructions) } } dockerfile-0.2.1/src/instructions.rs010064400007650000024000000326661340350762600157640ustar0000000000000000use std::{ borrow::Cow, convert, fmt, }; /// The `ADD` instruction copies new files, directories or remote file URLs from `` and adds /// them to the filesystem of the image at the path ``. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#add). pub struct Add(Cow<'static, str>); impl Add { pub fn new>>(val: T) -> Self { Add(val.into()) } } impl fmt::Display for Add { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ADD {}\n", &self.0) } } /// The `ARG` instruction defines a variable that users can pass at build-time to the builder with /// the `docker build` command using the `--build-arg =` flag. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#arg). pub struct Arg(Cow<'static, str>); impl Arg { pub fn new>>(val: T) -> Self { Arg(val.into()) } } impl fmt::Display for Arg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ARG {}\n", &self.0) } } /// The main purpose of a `CMD` is to provide defaults for an executing container. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#cmd). pub struct Cmd(Cow<'static, str>); impl Cmd { pub fn new>>(val: T) -> Self { Cmd(val.into()) } } impl fmt::Display for Cmd { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "CMD {}\n", &self.0) } } /// The `COPY` instruction copies new files or directories from `` and adds them to the /// filesystem of the container at the path ``. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#copy). pub struct Copy(Cow<'static, str>); impl Copy { pub fn new>>(val: T) -> Self { Copy(val.into()) } } impl fmt::Display for Copy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "COPY {}\n", &self.0) } } /// Parser directives are optional, and affect the way in which subsequent lines in a `Dockerfile` /// are handled. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#parser-directives). pub struct Directive(Cow<'static, str>); impl Directive { pub fn new>>(val: T) -> Self { Directive(val.into()) } } impl fmt::Display for Directive { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "# {}\n", &self.0) } } /// An `ENTRYPOINT` allows you to configure a container that will run as an executable. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#entrypoint). pub struct Entrypoint(Cow<'static, str>); impl Entrypoint { pub fn new>>(val: T) -> Self { Entrypoint(val.into()) } } impl fmt::Display for Entrypoint { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ENTRYPOINT {}\n", &self.0) } } /// The `ENV` instruction sets the environment variable `` to the value ``. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#env). pub struct Env(Cow<'static, str>); impl Env { pub fn new>>(val: T) -> Self { Env(val.into()) } } impl fmt::Display for Env { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ENV {}\n", &self.0) } } /// The `EXPOSE` instruction informs Docker that the container listens on the specified network /// ports at runtime. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#expose). pub struct Expose(Cow<'static, str>); impl Expose { pub fn new>>(val: T) -> Self { Expose(val.into()) } } impl fmt::Display for Expose { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "EXPOSE {}\n", &self.0) } } /// The `FROM` instruction initializes a new build stage and sets the base image for subsequent /// instructions. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#from). pub struct From(Cow<'static, str>); impl From { pub fn new>>(val: T) -> Self { From(val.into()) } } impl fmt::Display for From { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "FROM {}\n", &self.0) } } /// The `HEALTHCHECK` instruction tells Docker how to test a container to check that it is still /// working. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#healthcheck). pub struct Healthcheck(Cow<'static, str>); impl Healthcheck { pub fn new>>(val: T) -> Self { Healthcheck(val.into()) } } impl fmt::Display for Healthcheck { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "HEALTHCHECK {}\n", &self.0) } } /// The `LABEL` instruction adds metadata to an image. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#label). pub struct Label(Cow<'static, str>); impl Label { pub fn new>>(val: T) -> Self { Label(val.into()) } } impl fmt::Display for Label { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "LABEL {}\n", &self.0) } } /// The `ONBUILD` instruction adds to the image a trigger instruction to be executed at a later /// time, when the image is used as the base for another build. The trigger will be executed in /// the context of the downstream build, as if it had been inserted immediately after the `FROM` /// instruction in the downstream `Dockerfile`. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#onbuild). pub struct Onbuild(Cow<'static, str>); impl Onbuild { pub fn new>>(val: T) -> Self { Onbuild(val.into()) } } impl fmt::Display for Onbuild { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "ONBUILD {}\n", &self.0) } } /// The `RUN` instruction will execute any commands in a new layer on top of the current image and /// commit the results. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#run). pub struct Run(Cow<'static, str>); impl Run { pub fn new>>(val: T) -> Self { Run(val.into()) } } impl fmt::Display for Run { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "RUN {}\n", &self.0) } } /// The `SHELL` instruction allows the default shell used for the shell form of commands to be /// overridden. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#shell). pub struct Shell(Cow<'static, str>); impl Shell { pub fn new>>(val: T) -> Self { Shell(val.into()) } } impl fmt::Display for Shell { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SHELL {}\n", &self.0) } } /// The `STOPSIGNAL` instruction sets the system call signal that will be sent to the container /// to exit. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#stopsignal). pub struct Stopsignal(Cow<'static, str>); impl Stopsignal { pub fn new>>(val: T) -> Self { Stopsignal(val.into()) } } impl fmt::Display for Stopsignal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "STOPSIGNAL {}\n", &self.0) } } /// The `USER` instruction sets the user name (or UID) and optionally the user group (or GID) to /// use when running the image and for any `RUN`, `CMD` and `ENTRYPOINT` instructions that follow /// it in the `Dockerfile`. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#user). pub struct User(Cow<'static, str>); impl User { pub fn new>>(val: T) -> Self { User(val.into()) } } impl fmt::Display for User { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "USER {}\n", &self.0) } } /// The `VOLUME` instruction creates a mount point with the specified name and marks it as holding /// externally mounted volumes from native host or other containers. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#volume). pub struct Volume(Cow<'static, str>); impl Volume { pub fn new>>(val: T) -> Self { Volume(val.into()) } } impl fmt::Display for Volume { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "VOLUME {}\n", &self.0) } } /// The `WORKDIR` instruction sets the working directory for any `RUN`, `CMD`, `ENTRYPOINT`, /// `COPY` and `ADD` instructions that follow it in the `Dockerfile`. /// /// [See the docs here](https://docs.docker.com/engine/reference/builder/#workdir). pub struct Workdir(Cow<'static, str>); impl Workdir { pub fn new>>(val: T) -> Self { Workdir(val.into()) } } impl fmt::Display for Workdir { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "WORKDIR {}\n", &self.0) } } ////////////////////////////////////////////////////////////////////////////////////////////////// // Instruction Enum ////////////////////////////////////////////////////////////////////////////// /// An enum representing all of the different types of Dockerfile instructions available. /// /// This should be the full list according to the latest /// [Dockerfile spec here](https://docs.docker.com/engine/reference/builder/). If you notice any /// missing instructions, please /// [open an issue here](https://github.com/thedodd/dockerfile/issues/new). pub enum Instruction { Add(Add), Arg(Arg), Cmd(Cmd), Copy(Copy), Directive(Directive), Entrypoint(Entrypoint), Env(Env), Expose(Expose), From(From), Healthcheck(Healthcheck), Label(Label), Onbuild(Onbuild), Run(Run), Shell(Shell), Stopsignal(Stopsignal), User(User), Volume(Volume), Workdir(Workdir), } impl fmt::Display for Instruction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Instruction::Add(inst) => write!(f, "{}", inst), Instruction::Arg(inst) => write!(f, "{}", inst), Instruction::Cmd(inst) => write!(f, "{}", inst), Instruction::Copy(inst) => write!(f, "{}", inst), Instruction::Directive(inst) => write!(f, "{}", inst), Instruction::Entrypoint(inst) => write!(f, "{}", inst), Instruction::Env(inst) => write!(f, "{}", inst), Instruction::Expose(inst) => write!(f, "{}", inst), Instruction::From(inst) => write!(f, "{}", inst), Instruction::Healthcheck(inst) => write!(f, "{}", inst), Instruction::Label(inst) => write!(f, "{}", inst), Instruction::Onbuild(inst) => write!(f, "{}", inst), Instruction::Run(inst) => write!(f, "{}", inst), Instruction::Shell(inst) => write!(f, "{}", inst), Instruction::Stopsignal(inst) => write!(f, "{}", inst), Instruction::User(inst) => write!(f, "{}", inst), Instruction::Volume(inst) => write!(f, "{}", inst), Instruction::Workdir(inst) => write!(f, "{}", inst), } } } impl convert::From for Instruction { fn from(inst: Add) -> Self { Instruction::Add(inst) } } impl convert::From for Instruction { fn from(inst: Arg) -> Self { Instruction::Arg(inst) } } impl convert::From for Instruction { fn from(inst: Cmd) -> Self { Instruction::Cmd(inst) } } impl convert::From for Instruction { fn from(inst: Copy) -> Self { Instruction::Copy(inst) } } impl convert::From for Instruction { fn from(inst: Directive) -> Self { Instruction::Directive(inst) } } impl convert::From for Instruction { fn from(inst: Entrypoint) -> Self { Instruction::Entrypoint(inst) } } impl convert::From for Instruction { fn from(inst: Env) -> Self { Instruction::Env(inst) } } impl convert::From for Instruction { fn from(inst: Expose) -> Self { Instruction::Expose(inst) } } impl convert::From for Instruction { fn from(inst: From) -> Self { Instruction::From(inst) } } impl convert::From for Instruction { fn from(inst: Healthcheck) -> Self { Instruction::Healthcheck(inst) } } impl convert::From