├── .codeclimate.yml
├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── codeowners
├── pull_request_template.md
└── security
├── .gitignore
├── .gitmodules
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── bin
└── docker-template
├── comp
├── bin
├── list
└── list.pak
├── docker-template.gemspec
├── lib
├── docker
│ ├── template.rb
│ └── template
│ │ ├── auth.rb
│ │ ├── builder.rb
│ │ ├── builder
│ │ ├── normal.rb
│ │ ├── rootfs.rb
│ │ └── scratch.rb
│ │ ├── cache.rb
│ │ ├── cli.rb
│ │ ├── cli
│ │ ├── build.rb
│ │ ├── list.rb
│ │ └── run.rb
│ │ ├── error.rb
│ │ ├── logger.rb
│ │ ├── meta.rb
│ │ ├── notify.rb
│ │ ├── parser.rb
│ │ ├── repo.rb
│ │ └── version.rb
└── erb
│ └── context.rb
├── script
├── install
├── rake.d
│ └── update.rake
├── report
├── sync
└── test
├── spec
├── rspec
│ └── helper.rb
├── support
│ ├── clear.rb
│ ├── color.rb
│ ├── contexts
│ │ ├── docker.rb
│ │ └── repos.rb
│ ├── coverage.rb
│ ├── filter.rb
│ ├── matchers
│ │ └── have_const.rb
│ └── mocks
│ │ ├── container.rb
│ │ ├── image.rb
│ │ └── repo.rb
└── tests
│ └── lib
│ └── docker
│ ├── template
│ ├── auth_spec.rb
│ ├── builder
│ │ ├── normal_spec.rb
│ │ ├── rootfs_spec.rb
│ │ └── scratch_spec.rb
│ ├── builder_spec.rb
│ ├── cache_spec.rb
│ ├── cli
│ │ ├── build_spec.rb
│ │ └── list_spec.rb
│ ├── error_spec.rb
│ ├── logger_spec.rb
│ ├── meta_spec.rb
│ ├── notify_spec.rb
│ ├── parser_spec.rb
│ └── repo_spec.rb
│ └── template_spec.rb
└── templates
├── rootfs.erb
├── rootfs
├── alpine.erb
└── ubuntu.erb
└── scratch.erb
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 | plugins:
3 | rubocop:
4 | enabled: false
5 | ratings:
6 | paths:
7 | - "spec/tests/**.rb"
8 | - "lib/**.rb"
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | end_of_line = lf
9 | indent_size = 2
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Report an Issue!
4 | ---
5 |
6 |
12 |
13 | - [ ] I tried updating to the latest version
14 | - [ ] I can't, there is an issue
15 | - [ ] This is about an < latest
16 | - [ ] I understand older versions may be unsupported
17 | - [ ] I Am on Windows
18 | - [ ] Ubuntu Bash on Windows
19 | - [ ] Fedora Bash on Windows
20 | - [ ] Other Bash on Windows
21 | - [ ] I Am on Linux
22 | - [ ] Ubuntu
23 | - [ ] Fedora
24 | - [ ] CentOS
25 | - [ ] Redhat
26 | - [ ] Debian
27 | - [ ] I am on macOS 10.13
28 | - [ ] I am on macOS 10.14
29 | - [ ] I'm on Docker
30 | - [ ] I understand Docker may be unsupported
31 |
32 | ## Description
33 |
34 |
40 |
41 | ## Steps
42 |
43 | - Step 1
44 | - Step 2
45 | - Step 3
46 |
47 | ## Output
48 |
49 | ```sh
50 | # Doing stuff.
51 | # Doing more stuff.
52 | # Oh no error.
53 | ```
54 |
55 | ## Expected
56 |
57 |
62 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Suggest an Idea!
4 | ---
5 |
6 | - [ ] This feature is not on the latest version
7 |
8 | ## Request
9 |
10 |
15 |
16 | ## Examples
17 |
18 | ```sh
19 | # Your example
20 | # Maybe multiple
21 | # Examples?
22 | ```
23 |
--------------------------------------------------------------------------------
/.github/codeowners:
--------------------------------------------------------------------------------
1 | * @envygeeks
2 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | - [ ] I have added or updated the specs/tests.
2 | - [ ] I have verified that the specs/tests pass on my computer.
3 | - [ ] I have not attempted to bump, or alter versions.
4 | - [ ] This is a documentation change.
5 | - [ ] This is a source change.
6 |
7 | ## Description
8 |
9 |
15 |
--------------------------------------------------------------------------------
/.github/security:
--------------------------------------------------------------------------------
1 | * @envygeeks
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # --
2 | # OS Level
3 | # --
4 | .DS_Store
5 |
6 | # --
7 | # Ruby
8 | # --
9 | vendor/
10 | coverage/
11 | Gemfile.lock
12 | spec/fixture/site/
13 | spec/fixture/result/
14 | spec/fixture/out/
15 | .bundle/
16 | *.gem
17 |
18 | # --
19 | # CodeClimate
20 | # --
21 | cctr
22 |
23 | # --
24 | # Jekyll
25 | # --
26 | _site/
27 | .jekyll-cache/
28 | .jekyll-metadata
29 | .asset-cache/
30 | site/
31 |
32 | # --
33 | # Rails
34 | # --
35 | /db/*.sqlite3
36 | /npm-error.log
37 | .byebug_history
38 | /db/*.sqlite3-journal
39 | /config/application.yml
40 | /config/master.key
41 | /yarn-error.log
42 | /node_modules
43 | .pry_history
44 | /log
45 | /tmp
46 |
47 | # --
48 | # Yarn
49 | # --
50 | yarn.lock
51 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "examples/envygeeks"]
2 | path = examples/envygeeks
3 | url = https://github.com/envygeeks/docker.git
4 | [submodule "examples/jekyll.git"]
5 | path = examples/jekyll
6 | url = https://github.com/jekyll/docker.git
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - &ruby1 2.4
3 | - &ruby2 2.5
4 | notifications:
5 | email:
6 | - jordon@envygeeks.io
7 | branches:
8 | only:
9 | - master
10 | addons:
11 | code_climate:
12 | repo_token:
13 | secure: "\
14 | 3R+7bdlMXVNYtEI/3j+jycs1ENreTNny2sl19Too9VePSQyV2N1BCgyVqQU1H4Wj+5gS28\
15 | 2852KEVVtLDEngux7cwn1NfEi8Jo9mTCtYzB8SV2/kct+s2+xOL+ctSxXqPFTeHlaPA3PK\
16 | 93zx25Wcou64fNAZ7+xpegeWw901mIlLZfQ3uOAht7DENs+WKVzkpS3JwW9NUpSRpymk4d\
17 | AnYmr03eg825i7XOjin9aE8oAeHzfalCJtpCFm0BhAVWGSmTVmItcLVaFVpVfhwG1P3grq\
18 | wkyvZXFUBj2iZxuge2NzNabXMgOuoSOo3tQ8Abz1mgXLMtMuv+f63xVTEhHXTAK9QvEMLN\
19 | LeAmi6ubjfLHZ90ue6JgiHDB+GpuhmtAfPlmPCByJkR33RCV3Y+ktAkEkaqOWQA6g8acAZ\
20 | LJSoa2yiR7YcojF6C39ggd1Cy28HNd+PpRZzDJ2QJgu2JBWroZFukL13550Ir85isjb13X\
21 | BBNmrzLNdR9LumYAMGpoeEAps8skE01QJCWcCrA6X73ykkGjLrhExqbe984DJJ9pisJ7vp\
22 | PNwjdv8B95AXuE6ujflug/9LjUyevnGWPFmoRtgxj5CqzsyFRIthYMpZwFV6p0ff5Tq2Z6\
23 | HgRmvWpZ8AVc/n2EiobOZ1zEAt+gzykmO8+1BuT/xDsFdAa/+NO0w=\
24 | "
25 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 | ## Our Pledge
3 |
4 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
5 |
6 | ## Our Standards
7 |
8 | Examples of behavior that contributes to creating a positive environment include:
9 |
10 | * Gracefully accepting constructive criticism
11 | * Being respectful of differing viewpoints and experiences
12 | * Showing empathy towards other community members
13 | * Focusing on what is best for the community
14 | * Using welcoming and inclusive language
15 |
16 | Examples of unacceptable behavior by participants include:
17 |
18 | * Trolling, insulting/derogatory comments, and personal or political attacks
19 | * Publishing private info, such as a physical, or electronic address, without explicit permission
20 | * Other conduct which could reasonably be considered inappropriate in a professional setting
21 | * Sexualized language, or imagery, and unwelcome sexual attention or advances
22 | * Public, or private harassment
23 |
24 | ## Our Responsibilities
25 |
26 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
27 |
28 | ## Scope
29 |
30 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
31 |
32 | ## Enforcement
33 |
34 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at `jordon@envygeeks.io`. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
35 |
36 | ## Attribution
37 |
38 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4
39 | Available at [http://contributor-covenant.org/version/1/4][version]
40 |
41 | [homepage]: http://contributor-covenant.org
42 | [version]: http://contributor-covenant.org/version/1/4/
43 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | [@envygeeks]: https://github.com/envygeeks
2 |
3 | # Contributing
4 |
5 | **Hi!** *Thanks for considering a contribution to this project*. Below you will find a short set of guidelines to help you with your contribution. Be it a bug report, a pull request, or even a question. Let me also let you know that you don't have to file a pull-request, or contribute "raw code" to this project. I would never ask you to do that if you do not wish to, or currently are unable to. All I ask is that you file bug reports, and be respectful of the fact that this software is provided for free, on a donation basis, and at mostly [@envygeeks] expense via time (and sometimes cash.)
6 |
7 | ## Dependencies
8 |
9 | This project has an almost strict policy on dependencies. I do not do major updates (major version updates) of our current dependencies in anything but major releases, regardless of backwards compatibility. Dependencies are locked until the next major version. If there is a security reason for a major update, I ask that you file a bug with the upstream library and ask them to cut a minor release of the last release, which is proper.
10 |
11 | ***If they cannot provide a security update for the latest version, I will consider adding the major version as an optional version, but not restricting it as the only acceptable starting version, but I will complain about having to do so, because that's not cool.***
12 |
13 | ### Languages
14 |
15 | When it comes to the language versions I support, if a language version becomes EOL (end-of-life) before a major release can be made, I will remove all official support for it before a major release, and update the list of supported versions inside of the testing. This is done to encourage users to keep updating quickly, rather than remaining on old, unstable, and unsupported versions of the language.
16 |
17 | #### Policies
18 |
19 | I tend to test on more than one version, and more than one back version of any given language if a major update hasn't been done in a while, and continue to support them as long as they are not end-of-life. For example, if my software starts with Ruby `2.2`, and the version is currently `2.5`, and I have no plans for a major update you can add `2.5` to the list of supported Rubies, however, the next major update will drop it to `2.4`, and `2.5`. If `2.2` becomes end-of-life, I will also drop it from the list.
20 |
21 | * **Ruby:** Latest + 1 Back
22 | * **JRuby:** Latest Release ***only***
23 | * **Rubinius:** We do not support Rubinius at all.
24 | * **Node.js:** Latest LTS + Latest Release + 1 Back
25 | * **Go:** Latest + 1 Back
26 |
27 | ***It should be noted that if I wish to have a feature of a language before I can make a major release, I may, or may not go ahead and enforce a newer version of a language in a point release (exp: 3.x) so that I can update my code and clean it up.***
28 |
29 |
30 | ## Bugs/Features
31 |
32 | ***If you do not wish to (or are unable to) file a pull-request, you can file a bug report for any issue you have...*** as long as it is not a question. Issues include *random results*, *random errors*, *segfaults*, or anything of that nature. When you do, please make sure to include the system you are working from, the version of the language or compiler you are working with, and any relevant information that can be used to replicate the issue. ***Unreplicable issues will be closed.*** ***You can (and are encouraged to) ask questions if they are about something not documented, or if there is a question about an ambiguity in the documentation, this will prompt me to update the documentation to fix the problem.***
33 |
34 | ### What to not do
35 |
36 | * Ask me to put an urgency on your issue.
37 | * Be disrespectful: **I will block your comments**.
38 | * ":+1:" comments, I will lock issues; preventing further comments.
39 | * If you wish to "👍", "👎", or otherwise, please us the emoji-voting.
40 | * Ask if there are "any updates"
41 |
42 | **I do accept donations for fixing issues on a case-by-case urgency basis, as well as for creating features, if you need an issue addressed quickly. If you wish to do this you should contact [@envygeeks]. Otherwise issues are fixed based on complexity, time, and importance. All my projects get equal love, and sometimes it takes a minute to get back around.**
43 |
44 | ### Policies
45 | #### Closing
46 | * **Immediately:** `wontfix`, `stale`, `not-a-bug`
47 | * **Closed on Next Release:** `close-on-next-release`
48 | * **1 Week (7 Days)**: `pending-feedback`
49 |
50 | #### Fixing
51 | * **Bugfix x.x.X:** `bug`, `blocker`
52 | * **Minor x.X.x:** `non-blocker`, `bug`, `feature`
53 | * **Immediately:** `documentation`
54 |
55 | ## Pull Requests
56 | ### Tests
57 |
58 | If you change a method in any way, your tests ***must*** pass, and if there is an additional surface added, then you ***must add tests***, in Ruby I generally prefer to use RSpec, you should refer to RSpec's documentation for information on how it works, in Go, I prefer to use Go's native testing.
59 |
60 | * ***Ruby:*** `rake spec`
61 | * ***Go:*** `go test -cover -v`
62 | * `script/test`
63 |
64 | ### Code
65 |
66 | Code updates should follow the formatting of the given repository, formatting in Ruby is generally done via `rubocop` and is generally tied into `rake` and `rake spec`. Changes that are unrelated will be rejected and asked to be removed, regardless of your personal preference. You can always port those unrelated changes into another pull-request, unless they are arbitrary.
67 |
68 | #### Basics
69 |
70 | * Write your code in a clean and readable format.
71 | * Comment your code, at the high level, not lots of inline comments.
72 | * Stay at 80 lines if you can.
73 |
74 | ##### Good
75 |
76 | ```ruby
77 | # --
78 | # @param hello [String] your greeting
79 | # Allows you to send a greeting back to the user
80 | # @raise if the given object is not a string
81 | # @return [String] the greeting
82 | # --
83 | def greet(hello)
84 | raise ArgumentError, "needs string" unless hello.is_a?(String)
85 | alert hello, {
86 | class: ".greeting"
87 | }
88 | end
89 | ```
90 |
91 | ##### Bad
92 |
93 | ```ruby
94 | # --
95 | # @param hello [String] your greeting
96 | # Allows you to send a greeting back to the user
97 | # @return [String] the greeting
98 | # --
99 | def greet(hello)
100 | # @raise if the given object is not a string
101 | raise ArgumentError, "needs string" unless hello.is_a?(String)
102 | # Ship it to the user
103 | alert hello, {
104 | class: ".greeting"
105 | }
106 | end
107 | ```
108 |
109 | #### Commits
110 |
111 | Your pull-request should not add additional information outside of the Git-Commit. I understand this is Github, but explanitory data should remain in Git itself, not within Github (for the most part.) You should put the comment body of your pull request inside of your commit.
112 |
113 | ```
114 | Message
115 |
116 | This pull-request solves X issue because it was really buggy.
117 | Please do not add extra `\n\n` here because Github displays it
118 | badly, so just let it flow, besides, this is what was intended for
119 | Git anyways, you only keep your message at 80c.
120 | ```
121 |
122 |
123 | ### Documentation
124 |
125 | Documentation updates should follow the formatting of the given repository. You will be required to go through an approval process and even a comment processes. This process is mostly simple and should not impede a quick resolution. You should be prepared for the following:
126 |
127 | * Requests for unrelated changes to be removed.
128 | * Requests for language changes if the author feels it's ambiguous.
129 | * Requests for formatting changes.
130 | * Requests for `git squash`
131 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # ----------------------------------------------------------------------------
2 | # Frozen-string-literal: true
3 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
4 | # Encoding: utf-8
5 | # ----------------------------------------------------------------------------
6 |
7 | source "https://rubygems.org"
8 | gem "rake", :require => false
9 | gemspec
10 |
11 | group :test do
12 | gem "rspec", :require => false
13 | gem "luna-rspec-formatters", :require => false
14 | gem "memory_profiler", :require => false, :platform => :mri
15 | gem "rugged", :require => false, :platform => :mri
16 | gem "benchmark-ips", :require => false
17 | gem "rspec-helpers", :require => false
18 | gem "simplecov", :require => false
19 | end
20 |
21 | group :development do
22 | unless ENV["CI"]
23 | gem "pry", :require => false
24 | gem "msgpack", {
25 | :require => false
26 | }
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 - 2016 Jordon Bedwell
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Docker Template
2 |
3 | [][travis]
4 |
5 | [travis]: https://travis-ci.org/envygeeks/docker-template
6 |
7 | Docker Template is an organization and templating system for Docker images. A way to make your life easier and more organized by having repositories within repositories that share data among multiple sets of images. It is currently used to build all the images for Jekyll and EnvyGeeks. Please see https://github.com/envygeeks/docker-template/wiki for documentation.
8 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2017 - 2018 - MIT License
3 | # Source: https://github.com/envygeeks/devfiles
4 | # Author: Jordon Bedwell
5 | # Encoding: utf-8
6 |
7 | task default: [:spec]
8 | task(:spec) { exec "script/test" }
9 | task(:test) { exec "script/test" }
10 | Dir.glob("script/rake.d/*.rake").each do |v|
11 | load v
12 | end
13 |
--------------------------------------------------------------------------------
/bin/docker-template:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Frozen-string-literal: true
3 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
4 | # Encoding: utf-8
5 |
6 | STDOUT.sync = true
7 | STDERR.sync = true
8 | $LOAD_PATH.unshift(File.expand_path(
9 | "../lib", __dir__
10 | ))
11 |
12 | # --
13 |
14 | trap :SIGINT do
15 | $stderr.puts Simple::Ansi.red(
16 | "\nBye"
17 | )
18 |
19 | exit
20 | end
21 |
22 | # --
23 |
24 | begin
25 | require "docker/template/cli"
26 | rescue LoadError
27 | # Retry and let it fail if it doesn't work again.
28 | %w(bundler/setup docker/template/cli).each do |k|
29 | require k
30 | end
31 | end
32 |
33 | # Time to play the game.
34 | Docker::Template::CLI \
35 | .start
36 |
--------------------------------------------------------------------------------
/comp/bin:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | _docker_template() {
3 | comp=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/list
4 | COMPREPLY=($(compgen -W "$($comp ${COMP_WORDS[@]})" -- \
5 | ${COMP_WORDS[COMP_CWORD]}))
6 | }
7 |
8 | complete -F _docker_template \
9 | docker-template
10 |
--------------------------------------------------------------------------------
/comp/list:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # Frozen-string-literal: true
3 | # Copyright: 2016 Jordon Bedwell - MIT License
4 | # rubocop:disable Style/ElseAlignment
5 | # Encoding: utf-8
6 |
7 | ARGV.shift
8 | require "pathutil"
9 | require "msgpack"
10 |
11 | # --
12 |
13 | list = MessagePack.unpack(
14 | Pathutil.new(__dir__).expand_path.join("list.pak").read
15 | )
16 |
17 | # --
18 |
19 | if ARGV.first == "build"
20 | dir = Pathutil.new("repos").expand_path
21 |
22 | if dir.directory?
23 | list["build"] ||= { "_reply" => [] }
24 | list["build"]["_reply"].push(*dir.children.map(
25 | &:basename
26 | ))
27 | end
28 | end
29 |
30 | # --
31 |
32 | def key?(obj, key)
33 | obj["_reply"].include?(
34 | key
35 | )
36 | end
37 |
38 | # --
39 |
40 | def contains?(obj, key)
41 | result = obj["_reply"].grep(/#{Regexp.escape(
42 | key
43 | )}/)
44 |
45 | !result.empty?
46 | end
47 |
48 | # --
49 |
50 | def opt?(key)
51 | key =~ /\A-{1,2}/
52 | end
53 |
54 | # --
55 |
56 | if ARGV.empty?
57 | $stdout.puts list["_reply"].join(
58 | " "
59 | )
60 | else
61 | none = false
62 | rtrn = list
63 |
64 | ARGV.each_with_index do |key, index|
65 | if rtrn.key?(key) then rtrn = rtrn[key]
66 | elsif key?(rtrn, key) && !opt?(key) then none = true
67 | elsif index + 1 == ARGV.size && contains?(rtrn, key) then next
68 | elsif key?(rtrn, key) && opt?(key) then next
69 | else none = true
70 | end
71 | end
72 |
73 | unless none
74 | rtrn = rtrn["_reply"]
75 | $stdout.puts rtrn.join(
76 | " "
77 | )
78 | end
79 | end
80 |
--------------------------------------------------------------------------------
/comp/list.pak:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/envygeeks/docker-template/236bb64f43cf97ba6b86b2027500820d4c6ef6aa/comp/list.pak
--------------------------------------------------------------------------------
/docker-template.gemspec:
--------------------------------------------------------------------------------
1 | # ----------------------------------------------------------------------------
2 | # Frozen-string-literal: true
3 | # Copyright: 2015 Jordon Bedwell - Apache v2.0 License
4 | # Encoding: utf-8
5 | # ----------------------------------------------------------------------------
6 |
7 | $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
8 | require "docker/template/version"
9 |
10 | Gem::Specification.new do |spec|
11 | spec.authors = ["Jordon Bedwell"]
12 | spec.executables << "docker-template"
13 | spec.version = Docker::Template::VERSION
14 | spec.description = "Build and template awesome Docker images a variety of ways."
15 | spec.files = %W(Rakefile Gemfile README.md LICENSE) + Dir["{lib,bin,templates,comp}/**/*"]
16 | spec.summary = "Build and template Docker images a variety of ways."
17 | spec.homepage = "http://github.com/envygeeks/docker-template/"
18 | spec.required_ruby_version = ">= 2.1.0"
19 | spec.email = ["jordon@envygeeks.io"]
20 | spec.require_paths = ["lib"]
21 | spec.name = "docker-template"
22 | spec.license = "MIT"
23 | spec.bindir = "bin"
24 |
25 | spec.add_runtime_dependency("thor", "~> 0.19")
26 | spec.add_runtime_dependency("docker-api", "~> 1.28")
27 | spec.add_runtime_dependency("activesupport", ">= 4.2", "< 5.2")
28 | spec.add_runtime_dependency("simple-ansi", "~> 1.0")
29 | spec.add_runtime_dependency("pathutil", "~> 0.7")
30 | spec.add_runtime_dependency("extras", "~> 0.1")
31 | spec.add_runtime_dependency("json", ">= 1.8")
32 | end
33 |
--------------------------------------------------------------------------------
/lib/docker/template.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "docker"
6 | require "extras/all"
7 | require "erb/context"
8 | require "forwardable/extended"
9 | require "simple/ansi"
10 | require "pathutil"
11 | require "set"
12 |
13 | # --
14 |
15 | Excon.defaults[ :read_timeout] = 1440
16 | Excon.defaults[:write_timeout] = 1440
17 |
18 | # --
19 |
20 | module Docker
21 | module Template
22 | module_function
23 |
24 | # --
25 |
26 | def project?
27 | dir = root.join("docker")
28 | any = Builder.all.dup.keep_if(&:projects_allowed?)
29 | any = any.map(&:files).reduce(&:|).any? { |file| root.join(file).file? }
30 | return true if any && root.join(Meta.opts_file(:force => \
31 | :project)).file?
32 | end
33 |
34 | # --
35 |
36 | def root
37 | @root ||= begin
38 | Pathutil.new(Dir.pwd).realpath
39 | end
40 | end
41 |
42 | # --
43 |
44 | def gem_root
45 | @gem_root ||= begin
46 | Pathutil.new("../../").expand_path(
47 | __dir__
48 | )
49 | end
50 | end
51 |
52 | # --
53 |
54 | def template_root
55 | @template_root ||= begin
56 | gem_root.join("templates")
57 | end
58 | end
59 |
60 | # --
61 | # Pull a `template` from the `template_root` to parse it's data.
62 | # TODO: Rename this to get_template!
63 | # --
64 |
65 | def get(name, data = {})
66 | data = ERB::Context.new(data)
67 | template = template_root.join("#{name}.erb").read unless name.is_a?(Pathutil)
68 | template = name.read if name.is_a?(Pathutil)
69 | template = ERB.new(template)
70 |
71 | return template.result(
72 | data._binding
73 | )
74 | end
75 |
76 | # --
77 |
78 | def _require(what)
79 | require what
80 | if block_given?
81 | yield true
82 | end
83 | rescue LoadError
84 | if block_given?
85 | yield false
86 | end
87 | end
88 |
89 | # --
90 |
91 | def tmpdir
92 | if ENV["DOCKER_TEMPLATE_TMPDIR"]
93 | dir = Pathutil.new(ENV["DOCKER_TEMPLATE_TMPDIR"])
94 | .tap(&:mkdir_p)
95 | else
96 | dir = root.join("tmp")
97 | if !dir.exist?
98 | # Make the directory and then throw it out at exit.
99 | dir.mkdir_p; ObjectSpace.define_finalizer(dir, proc do
100 | dir.rm_rf
101 | end)
102 | end
103 |
104 | dir
105 | end
106 |
107 | dir.realpath
108 | end
109 | end
110 | end
111 |
112 | # --
113 | # Trick extras into merging array's into array's for us so users can inherit.
114 | # --
115 |
116 | class Array
117 | def deep_merge(new_)
118 | self | new_
119 | end
120 | end
121 |
122 | # --
123 |
124 | require "docker/template/error"
125 | require "docker/template/cache"
126 | require "docker/template/notify"
127 | require "docker/template/builder"
128 | require "docker/template/logger"
129 | require "docker/template/parser"
130 | require "docker/template/repo"
131 | require "docker/template/meta"
132 | require "docker/template/auth"
133 |
--------------------------------------------------------------------------------
/lib/docker/template/auth.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "open3"
6 | require "json"
7 |
8 | module Docker
9 | module Template
10 | class Auth
11 | DEFAULT_SERVER = "https://index.docker.io/v1/"
12 | def initialize(repo)
13 | @repo = repo
14 | end
15 |
16 | def auth_with_cmd?
17 | @repo.user =~ %r!/!
18 | end
19 |
20 | def auth_with_env?
21 | ENV.key?("DOCKER_USERNAME") && \
22 | ENV.key?("DOCKER_PASSWORD") && \
23 | ENV.key?("DOCKER_EMAIL")
24 | end
25 |
26 | # --
27 | def auth(skip: nil)
28 | return auth_from_cmd if auth_with_cmd? && skip != :cmd
29 | return auth_from_env if auth_with_env? && skip != :env
30 | auth_from_config
31 |
32 | # Wrap around their error to create ours.
33 | rescue Docker::Error::AuthenticationError
34 | raise Error::UnsuccessfulAuth
35 | # Something went wrong?
36 | end
37 |
38 | # --
39 | def auth_from_cmd
40 | case @repo.user
41 | when %r!^gcr\.io/! then auth_from_gcr
42 | else
43 | auth({
44 | skip: :cmd
45 | })
46 | end
47 | end
48 |
49 | # --
50 | def auth_from_env
51 | Docker.authenticate!({
52 | "username" => ENV["DOCKER_USERNAME"],
53 | "serveraddress" => ENV["DOCKER_SERVER"] || DEFAULT_SERVER,
54 | "password" => ENV["DOCKER_PASSWORD"],
55 | "email" => ENV["DOCKER_EMAIL"]
56 | })
57 | end
58 |
59 | # --
60 | def auth_from_config
61 | cred = Pathutil.new("~/.docker/config.json")
62 | cred = cred.expand_path.read_json
63 |
64 | unless cred.empty?
65 | cred["auths"].each do |server, info|
66 | next if info.empty?
67 |
68 | user, pass = Base64.decode64(info["auth"]).split(":", 2)
69 | Docker.authenticate!({
70 | "username" => user,
71 | "serveraddress" => server,
72 | "email" => info["email"],
73 | "password" => pass
74 | })
75 | end
76 | end
77 | end
78 |
79 | private
80 | def auth_from_gcr
81 | i, o, e, = Open3.popen3("docker-credential-gcr get")
82 | server, = @repo.user.split("/", 2)
83 |
84 | i.puts server; i.close
85 | val = JSON.parse(o.read.chomp)
86 | [o, e].map(&:close)
87 |
88 | if val
89 | Docker.authenticate!({
90 | "serveraddress" => server,
91 | "username" => val["Username"],
92 | "email" => "docker-template+opensource@envygeeks.io",
93 | "password" => val["Secret"],
94 | })
95 | end
96 | end
97 | end
98 | end
99 | end
100 |
--------------------------------------------------------------------------------
/lib/docker/template/builder.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class Builder
8 | extend Forwardable::Extended
9 |
10 | # --
11 |
12 | attr_reader :repo
13 | attr_reader :context
14 | attr_reader :copy
15 | attr_reader :img
16 |
17 | # --
18 |
19 | ALIAS_SETUP = [:cache_context]
20 | SETUP = [:setup_context, :copy_global, :copy_project,
21 | :copy_all, :copy_group, :copy_tag, :copy_cleanup, :copy_git, :build_context,
22 | :verify_context, :cache_context].freeze
23 |
24 | # --
25 |
26 | def initialize(repo)
27 | @repo = repo
28 | end
29 |
30 | # --
31 | # Checks to see if this repository is an alias. This happens when the
32 | # user has alised data inside of their configuration file. At this point
33 | # we will not only copy the parent's data but the aliased data.
34 | # --
35 |
36 | def alias?
37 | !@repo.complex_alias? && @repo.alias? && !rootfs?
38 | end
39 |
40 | # --
41 |
42 | def rootfs?
43 | is_a?(
44 | Rootfs
45 | )
46 | end
47 |
48 | # --
49 |
50 | def normal?
51 | @repo.type == "normal" \
52 | && !rootfs?
53 | end
54 |
55 | # --
56 |
57 | def scratch?
58 | @repo.type == "scratch" \
59 | && !rootfs?
60 | end
61 |
62 | # --
63 |
64 | def aliased_img
65 | if alias?
66 | then @aliased_img ||= begin
67 | Docker::Image.get(
68 | aliased_repo ? aliased_repo.to_s : aliased_tag
69 | )
70 | end
71 | end
72 |
73 | rescue Docker::Error::NotFoundError
74 | if alias?
75 | nil
76 | end
77 | end
78 |
79 | # --
80 |
81 | def push
82 | return if rootfs? || !@repo.pushable?
83 |
84 | Notify.push(self)
85 | Auth.new(@repo).auth
86 | img = @img || Image.get(@repo.to_s)
87 | img.push nil, :repo_tag => @repo.to_s, \
88 | &Logger.new(repo).method(:api)
89 |
90 | rescue Docker::Error::NotFoundError
91 | $stderr.puts Simple::Ansi.red(
92 | "Image does not exist, unpushable."
93 | )
94 | end
95 |
96 | # --
97 |
98 | def build
99 | Simple::Ansi.clear if @repo.buildable?
100 | return build_alias if alias?
101 | setup
102 |
103 | if @repo.buildable?
104 | then Notify.build(@repo, :rootfs => rootfs?) do
105 | chdir_build
106 | end
107 | end
108 |
109 | push
110 | rescue SystemExit => exit_
111 | teardown :img => true
112 | raise exit_
113 | ensure
114 | if !rootfs?
115 | teardown else teardown({
116 | :img => false
117 | })
118 | end
119 | end
120 |
121 | # --
122 | # This method is a default reference. It is called when the image is
123 | # done building or when there is an error and we need to clean up some
124 | # stuff before exiting, use it... please.
125 | # --
126 |
127 | def teardown(*_)
128 | $stderr.puts Ansi.red(
129 | "#{__method__}: Not Implemented."
130 | )
131 | end
132 |
133 | # --
134 | # The prebuild happens when a user has "setup_context", which typically
135 | # only happens with scratch, which will prebuild it's rootfs image so
136 | # it can get to building it's actual image.
137 | # --
138 |
139 | def setup
140 | unless respond_to?(:setup_context, true)
141 | raise Error::NoSetupContext
142 | end
143 |
144 | SETUP.map do |val|
145 | if respond_to?(val, true)
146 | send(val)
147 | end
148 | end
149 | end
150 |
151 | # --
152 |
153 | private
154 | def build_alias
155 | alias_setup
156 |
157 | if @repo.buildable?
158 | if (repo = aliased_repo)
159 | aliased = self.class.new(repo)
160 | unless aliased_img
161 | aliased.build
162 | end
163 |
164 | elsif !aliased_img
165 | raise(
166 | Error::ImageNotFound, aliased_tag
167 | )
168 | end
169 |
170 | Notify.alias(self)
171 | aliased_img.tag(
172 | @repo.to_tag_h
173 | )
174 | end
175 |
176 | push
177 | end
178 |
179 | # --
180 |
181 | private
182 | def alias_setup
183 | ALIAS_SETUP.map do |m|
184 | if respond_to?(m, true)
185 | send(m)
186 | end
187 | end
188 | end
189 |
190 | # --
191 |
192 | private
193 | def chdir_build
194 | @context.chdir do
195 | logger = Logger.new(repo).method(:api)
196 | opts = {
197 | :force => @repo.meta.force?,
198 | :t => @repo.to_s(rootfs: rootfs?),
199 | :squash => @repo.meta.squash?,
200 | :nocache => @repo.meta.force?
201 | }
202 |
203 | if @repo.meta["tty"]
204 | $stderr.puts Simple::Ansi.yellow(
205 | "TTY not supported: Ignored."
206 | )
207 | end
208 |
209 | @img = Docker::Image.build_from_dir(".",
210 | opts, &logger
211 | )
212 | end
213 | end
214 |
215 | # --
216 |
217 | private
218 | def cache_context
219 | if repo.cacheable?
220 | $stderr.puts Simple::Ansi.red(
221 | "Context caching not supported"
222 | )
223 | end
224 | end
225 |
226 | # --
227 | # Copy any git repositories the user wishes us to copy.
228 | # --
229 |
230 | private
231 | def copy_git
232 | return if rootfs? || !@repo.meta.git? || @repo.meta.push_only?
233 | require "rugged"
234 |
235 | repos = @repo.meta[:git]
236 | repos = repos.for_all + (repos.by_tag || []) +
237 | (repos.by_type || [])
238 |
239 | repos.each do |repo|
240 | credentials = Rugged::Credentials::SshKey.new({
241 | :privatekey => Pathutil.new(repo[:key]).expand_path.to_s,
242 | :publickey => Pathutil.new(repo[:pub]).expand_path.to_s,
243 | :username => repo[:user]
244 | })
245 |
246 | dir = @copy.join(repo[:clone_to])
247 | if !dir.exist?
248 | $stderr.puts Simple::Ansi.green("Cloning #{repo[:repo]} to #{repo[:clone_to]}.")
249 | Rugged::Repository.clone_at(repo[:repo], dir.to_s, {
250 | :credentials => credentials
251 | })
252 | else
253 | $stderr.puts Simple::Ansi.yellow(
254 | "Skipping #{repo[:repo]}, exists already."
255 | )
256 | end
257 | end
258 | end
259 |
260 | # --
261 | # The root can have it's own global copy directory shared across all
262 | # repos in your repo container dir so this encapsulates those.
263 | # --
264 |
265 | private
266 | def copy_global
267 | unless rootfs?
268 | dir = Template.root.join(
269 | @repo.meta["copy_dir"]
270 | )
271 |
272 | if dir.exist?
273 | then dir.safe_copy(
274 | @copy, :root => Template.root
275 | )
276 | end
277 | end
278 | end
279 |
280 | # --
281 |
282 | private
283 | def copy_project
284 | if Template.project?
285 | ignores = repo.meta["project_copy_ignore"].map do |path|
286 | Pathutil.new(path).expand_path(
287 | Template.root
288 | )
289 | end
290 |
291 | Template.root.safe_copy(
292 | context.join(repo.meta.project_copy_dir), {
293 | :root => Template.root, :ignore => ignores
294 | }
295 | )
296 | end
297 | end
298 |
299 | # --
300 |
301 | private
302 | def copy_tag
303 | unless rootfs?
304 | dir = @repo.copy_dir("tag", @repo.tag)
305 |
306 | if dir.exist?
307 | then dir.safe_copy(
308 | @copy, :root => Template.root
309 | )
310 | end
311 | end
312 | end
313 |
314 | # --
315 |
316 | private
317 | def copy_group
318 | build_group = @repo.meta["tags"][
319 | @repo.tag
320 | ]
321 |
322 | unless rootfs? || !build_group
323 | dir = @repo.copy_dir("group", build_group)
324 |
325 | if dir.exist?
326 | then dir.safe_copy(
327 | @copy, :root => Template.root
328 | )
329 | end
330 | end
331 | end
332 |
333 | # --
334 |
335 | private
336 | def copy_all
337 | unless rootfs?
338 | dir = @repo.copy_dir("all")
339 |
340 | if dir.exist?
341 | then dir.safe_copy(
342 | @copy, :root => Template.root
343 | )
344 | end
345 | end
346 | end
347 |
348 | # --
349 |
350 | rb_delegate :aliased_tag, :to => "repo.meta"
351 | rb_delegate :aliased_repo, {
352 | :to => :repo, :alias_of => :aliased
353 | }
354 |
355 | class << self
356 |
357 | # --
358 | # REFERENCE METHOD: This is here to let you know we access files.
359 | # --
360 |
361 | def files
362 | return [
363 | #
364 | ]
365 | end
366 |
367 | # --
368 |
369 | def projects_allowed!
370 | return @projects_allowed \
371 | = true
372 | end
373 |
374 | # --
375 |
376 | def projects_allowed?
377 | return !!@projects_allowed
378 | end
379 |
380 | # --
381 |
382 | def sub?
383 | false
384 | end
385 |
386 | # --
387 |
388 | def inherited(klass)
389 | (@sub_classes ||= []).push(
390 | klass
391 | )
392 | end
393 |
394 | # --
395 |
396 | def all
397 | @sub_classes ||= [
398 | #
399 | ]
400 | end
401 | end
402 | end
403 | end
404 | end
405 |
406 | require "docker/template/builder/rootfs"
407 | require "docker/template/builder/scratch"
408 | require "docker/template/builder/normal"
409 |
--------------------------------------------------------------------------------
/lib/docker/template/builder/normal.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class Builder
8 | class Normal < Builder
9 | projects_allowed!
10 |
11 | def teardown(img: false)
12 | @img.delete "force" => true if @img && img
13 | @context.rmtree if @context && \
14 | @context.directory?
15 | end
16 |
17 | # --
18 |
19 | def setup_context
20 | @context = @repo.tmpdir
21 | @copy = @context.join("copy")
22 | copy_dockerfile
23 | @copy.mkdir
24 | end
25 |
26 | # --
27 |
28 | private
29 | def copy_dockerfile
30 | dockerfile = Template.project?? Template.root : @repo.root
31 | dockerfile = dockerfile.join("Dockerfile").read
32 |
33 | data = ERB::Context.new(:meta => @repo.meta)
34 | data = ERB.new(dockerfile).result(data._binding)
35 | context = @context.join("Dockerfile")
36 | context.write(data)
37 | end
38 |
39 | # --
40 |
41 | private
42 | def cache_context
43 | return unless @repo.cacheable?
44 | return Cache.aliased_context(self) if alias?
45 | Cache.context(self, @context)
46 | end
47 |
48 | class << self
49 | def files
50 | %w(
51 | Dockerfile
52 | )
53 | end
54 | end
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/docker/template/builder/rootfs.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class Builder
8 | class Rootfs < Builder
9 | extend Forwardable::Extended
10 |
11 | # --
12 |
13 | def data
14 | Template.get(:rootfs, {
15 | :rootfs_base_img => @repo.meta["rootfs_base_img"]
16 | })
17 | end
18 |
19 | # --
20 |
21 | def discover
22 | self.class.files.map do |file|
23 | file = @repo.root.join(
24 | file
25 | )
26 |
27 | if file.file?
28 | then return file
29 | end
30 | end
31 |
32 | "rootfs/#{
33 | @repo.meta.rootfs_template
34 | }"
35 | end
36 |
37 | # --
38 |
39 | def builder_data
40 | Template.get(discover, {
41 | :meta => @repo.meta
42 | })
43 | end
44 |
45 | # --
46 | # During a simple copy you store all the data (including rootfs) data
47 | # as a project unit, this helps us clean up data that is known to be for
48 | # just the rootfs image and remove it so it doesn't impact.
49 | # --
50 | def simple_cleanup(dir)
51 | file = dir.join("usr/local/bin/mkimg")
52 |
53 | if file.exist?
54 | then file.delete
55 | end
56 | end
57 |
58 | # --
59 |
60 | def teardown(img: true)
61 | @context.rmtree if @context && @context.directory?
62 | @img.delete "force" => true if @img && img \
63 | rescue nil
64 | end
65 |
66 | # --
67 |
68 | private
69 | def setup_context
70 | @context = @repo.tmpdir("rootfs")
71 | @copy = @context.join(@repo.meta["copy_dir"])
72 | @context.join("Dockerfile").write(data)
73 |
74 | @copy.join("usr/local/bin").mkdir_p
75 | @copy.join("usr/local/bin/mkimg").write(builder_data)
76 | @copy.join("usr/local/bin/mkimg").chmod(0755)
77 | copy_rootfs
78 | end
79 |
80 | # --
81 |
82 | private
83 | def copy_rootfs
84 | dir = @repo.copy_dir(
85 | "rootfs"
86 | )
87 |
88 | if dir.exist?
89 | @repo.copy_dir("rootfs").safe_copy(@copy, {
90 | :root => Template.root
91 | })
92 | end
93 | end
94 |
95 | class << self
96 | def sub?
97 | return true
98 | end
99 |
100 | # --
101 |
102 | def files
103 | %w(
104 | Rootfs.erb Rootfs rootfs.erb rootfs
105 | )
106 | end
107 | end
108 | end
109 | end
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/lib/docker/template/builder/scratch.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class Builder
8 | class Scratch < Builder
9 | attr_reader :rootfs
10 |
11 | # --
12 |
13 | def initialize(*args)
14 | super; @rootfs = Rootfs.new(
15 | repo
16 | )
17 | end
18 |
19 | # --
20 |
21 | def data
22 | Template.get(:scratch, {
23 | :entrypoint => @repo.meta.entry,
24 | :maintainer => @repo.meta.maintainer,
25 | :tar_gz => @tar_gz.basename
26 | })
27 | end
28 |
29 | # --
30 |
31 | def teardown(img: false)
32 | @copy.rm_rf if @copy
33 | @context.rm_rf if @context
34 | @tar_gz.rm_rf if @tar_gz
35 |
36 | if @img && img
37 | then @img.delete({
38 | "force" => true
39 | })
40 | end
41 | rescue Docker::Error::NotFoundError
42 | nil
43 | end
44 |
45 | # --
46 |
47 | private
48 | def setup_context
49 | @context = @repo.tmpdir
50 | @tar_gz = @repo.tmpfile "archive", ".tar.gz", root: @context
51 | @copy = @repo.tmpdir "copy"
52 | copy_dockerfile
53 | end
54 |
55 | # --
56 |
57 | private
58 | def copy_dockerfile
59 | data = self.data % @tar_gz.basename
60 | dockerfile = @context.join("Dockerfile")
61 | dockerfile.write(data)
62 | end
63 |
64 | # --
65 |
66 | def copy_cleanup
67 | @rootfs.simple_cleanup(
68 | @copy
69 | )
70 | end
71 |
72 | # --
73 |
74 | def verify_context
75 | if @repo.buildable? && @tar_gz.zero?
76 | raise Error::InvalidTargzFile, @tar_gz
77 | end
78 | end
79 |
80 | # --
81 |
82 | private
83 | def build_context
84 | return unless @repo.buildable?
85 | @rootfs.build
86 |
87 | logger = Logger.new
88 | img = Container.create(create_args)
89 | img.start.attach(logger_opts, &logger.method(logger_type))
90 | status = img.json["State"]["ExitCode"]
91 |
92 | if status != 0
93 | logger.simple(:stderr, img.logs(:stderr => true)) unless logger.output?
94 | logger.simple(:stdout, img.logs(:stdout => true)) unless logger.output?
95 | raise Error::BadExitStatus, status
96 | end
97 | ensure
98 | if img
99 | then img.tap(&:stop).delete({
100 | "force" => true
101 | })
102 | end
103 |
104 | @rootfs.teardown
105 | end
106 |
107 | # --
108 |
109 | private
110 | def logger_type
111 | @repo.meta["tty"] ? :tty : :simple
112 | end
113 |
114 | # --
115 |
116 | private
117 | def logger_opts
118 | return {
119 | :tty => @repo.meta["tty"], :stdout => true, :stderr => true
120 | }
121 | end
122 |
123 | # --
124 |
125 | private
126 | def create_args
127 | name = ["rootfs", @repo.name, @repo.tag, "image"].join("-")
128 | env = @repo.to_env(:tar_gz => @tar_gz, :copy_dir => @copy)
129 |
130 | return {
131 | "Env" => env.to_a,
132 | "Tty" => @repo.meta["tty"],
133 | "Image" => @rootfs.img.id,
134 | "Name" => name,
135 |
136 | "HostConfig" => {
137 | "Binds" => [
138 | "#{@copy}:#{@copy}", "#{@tar_gz}:#{@tar_gz}"
139 | ]
140 | },
141 |
142 | "Volumes" => {
143 | @copy.to_s => {
144 | "source" => @copy.to_s,
145 | "destination" => @copy.to_s
146 | },
147 |
148 | @tar_gz.to_s => {
149 | "source" => @tar_gz.to_s,
150 | "destination" => @tar_gz.to_s
151 | }
152 | }
153 | }
154 | end
155 |
156 | class << self
157 | def files
158 | return Rootfs \
159 | .files
160 | end
161 | end
162 | end
163 | end
164 | end
165 | end
166 |
--------------------------------------------------------------------------------
/lib/docker/template/cache.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | module Cache
8 | module_function
9 |
10 | # --
11 | # Cache the context into the cache directory.
12 | # --
13 |
14 | def context(builder, context)
15 | builder.repo.cache_dir.rm_rf
16 | $stderr.puts Simple::Ansi.yellow(format("Copying context for %s", builder.repo))
17 | cache_dir = builder.repo.cache_dir
18 | cache_dir.parent.mkdir_p
19 |
20 | context.cp_r(cache_dir.tap(
21 | &:rm_rf
22 | ))
23 |
24 | readme(builder)
25 | end
26 |
27 | # --
28 | # rubocop:disable Metrics/LineLength
29 | # --
30 |
31 | def aliased_context(builder)
32 | if builder.aliased_repo.cache_dir.exist?
33 | $stderr.puts Simple::Ansi.yellow(format("Copying %s context to %s", builder.aliased_repo, builder.repo))
34 | builder.aliased_repo.cache_dir.cp_r(builder.repo.cache_dir.tap(
35 | &:rm_rf
36 | ))
37 | end
38 | end
39 |
40 | # --
41 | # Cleanup the context caches, removing the caches we no longer need.
42 | # rubocop:enable Metrics/LineLength
43 | # --
44 |
45 | def cleanup(repo)
46 | return unless repo.clean_cache?
47 | cache_dir = repo.cache_dir.parent
48 |
49 | if cache_dir.exist?
50 | cache_dir.children.each do |file|
51 | next unless repo.meta.tags.include?(file.basename)
52 | $stdout.puts Simple::Ansi.yellow(format("Removing %s.",
53 | file.relative_path_from(Template.root)
54 | ))
55 |
56 | file.rm_rf
57 | end
58 | end
59 | end
60 |
61 | # --
62 | # Note: We normally expect but do not require you to have a README.
63 | # Search for and copy the readme if available.
64 | # --
65 |
66 | def readme(builder)
67 | return unless file = builder.repo.root.children.find { |val| val =~ /readme/i }
68 | file.safe_copy(builder.repo.cache_dir, {
69 | :root => file.parent
70 | })
71 | end
72 | end
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/lib/docker/template/cli.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "docker/template"
6 | require "thor"
7 |
8 | module Docker
9 | module Template
10 | class CLI < Thor
11 |
12 | # --
13 |
14 | option :force, :type => :boolean, :desc => "Force caching."
15 | desc "cache [REPOS [OPTS]]", "Cache all (or some) of your repositories."
16 | option :clean, :type => :boolean, :desc => "Cleanup your caches."
17 | option :help, :type => :boolean, :desc => "Output this."
18 |
19 | # --
20 |
21 | def cache(*args)
22 | return help(__method__) if options.help?
23 | self.options = options.merge(:cache => true) if options.force?
24 | self.options = options.merge(:cache_only => true)
25 | return build(
26 | *args
27 | )
28 | end
29 |
30 | # --
31 |
32 | option :force, :type => :boolean, :desc => "Force cleaning."
33 | desc "clean [REPOS [OPTS]]", "Clean all (or some) of your repositories caches."
34 | option :help, :type => :boolean, :desc => "Output this."
35 |
36 | # --
37 |
38 | def clean(*args)
39 | return help(__method__) if options.help?
40 | self.options = options.merge(:clean => true) if options.force?
41 | self.options = options.merge(:clean_only => true)
42 | return build(
43 | *args
44 | )
45 | end
46 |
47 | # --
48 |
49 | option :force, :type => :boolean, :desc => "Force cleaning."
50 | desc "push [REPOS [OPTS]]", "Push all (or some) of your repositories."
51 | option :help, :type => :boolean, :desc => "Output this."
52 |
53 | # --
54 |
55 | def push(*args)
56 | return help(__method__) if options.help?
57 | self.options = options.merge(:push => true) if options.force?
58 | self.options = options.merge(:push_only => true)
59 | return build(
60 | *args
61 | )
62 | end
63 |
64 | # --
65 | # docker-template build [repos [opts]]
66 | # --
67 |
68 | desc "build [REPOS [OPTS]]", "Build all (or some) of your repositories."
69 | option :profile, :type => :boolean, :desc => "Profile Memory."
70 | option :tty, :type => :boolean, :desc => "Enable TTY Output."
71 | option :cache, :type => :boolean, :desc => "Cache your repositories to cache."
72 | option :exclude, :type => :array, :desc => "Build everything except for these images."
73 | option :debug, :type => :boolean, :desc => "Send the DEBUG=true env var to your instance."
74 | option :diff, :type => :boolean, :desc => "Build only modified repositories."
75 | option :push, :type => :boolean, :desc => "Push Repo After Building."
76 | option :clean, :type => :boolean, :desc => "Cleanup your caches."
77 | option :force, :type => :boolean, :desc => "Force your build."
78 | option :squash, :type => :boolean, :desc => "Squash the build."
79 | option :help, :type => :boolean, :desc => "Output this."
80 |
81 | # --
82 | # rubocop:disable Lint/RescueException
83 | # --
84 |
85 | def build(*args)
86 | return help(__method__) if options.help?
87 | Build.new(args, options)
88 | .start
89 |
90 | rescue Docker::Template::Error::StandardError => e
91 | $stderr.puts Simple::Ansi.red(e.message)
92 | exit e.respond_to?(:status) ? \
93 | e.status : 1
94 |
95 | rescue Exception => _e
96 | raise unless $ERROR_POSITION
97 | $ERROR_POSITION.delete_if do |source|
98 | source =~ %r!#{Regexp.escape(
99 | __FILE__
100 | )}!o
101 | end
102 | end
103 |
104 | # --
105 | # rubocop:enable Lint/RescueException
106 | # docker-template list [options]
107 | # --
108 |
109 | option :help, :type => :boolean, :desc => "Output this."
110 | desc "list [OPTS]", "List all possible builds."
111 |
112 | # --
113 |
114 | def list
115 | return help(__method__) if options.help?
116 | return $stdout.puts(
117 | List.build
118 | )
119 | end
120 | end
121 | end
122 | end
123 |
124 | require "docker/template/cli/build"
125 | require "docker/template/cli/list"
126 |
--------------------------------------------------------------------------------
/lib/docker/template/cli/build.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class CLI
8 | class Build
9 | def initialize(args, opts)
10 | @opts = Meta.new(opts || {})
11 | @repos = Parser.new(args, opts || {}).parse
12 | @args = args
13 | end
14 |
15 | # --
16 |
17 | def start
18 | _profile do
19 | changed! if @opts.diff?
20 | exclude! if @opts.exclude?
21 | @repos.tap { |o| o.map(&:build) }.uniq(&:name).map(
22 | &:clean
23 | )
24 | end
25 | end
26 |
27 | # --
28 |
29 | def exclude!
30 | Parser.new(@opts[:exclude].map { |v| v.split(/,\s*/) }.flatten.compact).parse.each do |repo|
31 | @repos.delete_if do |v|
32 | v.name == repo.name && v.tag == repo.tag
33 | end
34 | end
35 | end
36 |
37 | # --
38 | # rubocop:disable Metrics/AbcSize
39 | # --
40 |
41 | def changed!
42 | Template._require "rugged" do |loaded|
43 | return true unless loaded
44 |
45 | git = Rugged::Repository.new(Template.root.to_s)
46 | dir = Template.root.join(@opts.repos_dir)
47 |
48 | repos = git.last_commit.diff.each_delta.each_with_object(Set.new) do |delta, set|
49 | next unless Pathutil.new(delta.new_file[:path]).expand_path(Template.root).in_path?(dir)
50 | set.merge(delta.new_file[:path].split("/").values_at(
51 | 1
52 | ))
53 | end
54 |
55 | @repos = @repos.select do |repo|
56 | repos.include?(
57 | repo.name
58 | )
59 | end
60 | end
61 | end
62 |
63 | # --
64 | # rubocop:enable Metrics/AbcSize
65 | # --
66 |
67 | private
68 | def _profile
69 | return yield unless @opts.profile?
70 | Template._require "memory_profiler" do
71 | profiler = MemoryProfiler.report(:top => 10_240) { yield }
72 | profiler.pretty_print({
73 | :to_file => "profile.txt"
74 | })
75 | end
76 | end
77 | end
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/lib/docker/template/cli/list.rb:
--------------------------------------------------------------------------------
1 | module Docker
2 | module Template
3 | class CLI
4 | class List
5 | def self.build
6 | return new.build
7 | end
8 |
9 | # --
10 |
11 | def initialize(images = Parser.new([], {}).parse)
12 | @images = images
13 | end
14 |
15 | # --
16 |
17 | def build
18 | out = ""
19 |
20 | @images.group_by(&:user).each do |user, images|
21 | out += "[user] " + Simple::Ansi.blue(user) + "\n" + repos(
22 | user, images
23 | )
24 | end
25 |
26 | out
27 | end
28 |
29 | # --
30 |
31 | def repos(user, images)
32 | out = ""
33 |
34 | images.group_by(&:name).each do |name, _|
35 | out += " ├─ [repo] " + Simple::Ansi.green(name) + "\n"
36 | out += tags(user, name, images)
37 | out += remote_aliases(
38 | user, name, images
39 | )
40 | end
41 |
42 | out
43 | end
44 |
45 | # --
46 |
47 | def tags(user, name, images)
48 | out = ""
49 |
50 | images.select { |image| image.name == name && image.user == user && !image.alias? }.each do |image|
51 | out += " │ ├─ [tag] " + Simple::Ansi.magenta(image.tag) + "\n"
52 | out += aliases(
53 | user, name, image.tag, images
54 | )
55 | end
56 |
57 | out
58 | end
59 |
60 | # --
61 |
62 | def remote_aliases(*args)
63 | out = ""
64 |
65 | remotes = _remote_aliases(*args).group_by do |image|
66 | image.meta[:aliases][
67 | image.tag
68 | ]
69 | end
70 |
71 | remotes.each do |remote, images_|
72 | out += " │ ├─ [remote] "
73 | out += Simple::Ansi.yellow(remote)
74 | out += "\n"
75 |
76 | images_.each do |image|
77 | out += " │ │ ├─ [alias] "
78 | out += Simple::Ansi.yellow(
79 | image.tag
80 | )
81 |
82 | out += "\n"
83 | end
84 | end
85 |
86 | out
87 | end
88 |
89 | # --
90 |
91 | def _remote_aliases(user, name, images)
92 | images.select do |image|
93 | image.user == user && image.name == name && aliased_remote?(
94 | image
95 | )
96 | end
97 | end
98 |
99 | # --
100 |
101 | def aliases(user, name, tag, images, depth: 0)
102 | out = ""
103 |
104 | _aliases(user, name, tag, images).each do |alias_|
105 | name_ = \
106 | if alias_.name == name
107 | Simple::Ansi.yellow(
108 | alias_.tag
109 | )
110 |
111 | else
112 | Simple::Ansi.yellow(
113 | "#{alias_.name}:#{alias_.tag}"
114 | )
115 | end
116 |
117 | out += " │ │ #{"│ " * depth}├─ [alias] #{name_}\n"
118 | out += aliases(user, name, alias_.tag, images, {
119 | :depth => depth + 1
120 | })
121 | end
122 |
123 | out
124 | end
125 |
126 | # --
127 |
128 | private
129 | def aliased_remote?(image)
130 | return image.alias? && !image.aliased
131 | end
132 |
133 | # --
134 |
135 | private
136 | def _aliases(user, name, tag, images)
137 | images.select do |image|
138 | image.alias? && image.aliased && image.aliased.tag == tag \
139 | && image.aliased.name == name && image.aliased.user \
140 | == user
141 | end
142 | end
143 | end
144 | end
145 | end
146 | end
147 |
--------------------------------------------------------------------------------
/lib/docker/template/cli/run.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2017 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class CLI
8 | class Run
9 | def initialize(args, opts)
10 | @opts = Meta.new(opts || {})
11 | @repos = Parser.new(args, opts || {}).parse
12 | @args = args
13 | end
14 |
15 | # --
16 |
17 | def start
18 | _profile do
19 | @repos.tap do |o|
20 | o.map do |r|
21 | r.template
22 | $stdout.puts(
23 | r.tmpdir
24 | )
25 | end
26 | end
27 | end
28 | end
29 |
30 | # --
31 | # rubocop:enable Metrics/AbcSize
32 | # --
33 |
34 | private
35 | def _profile
36 | return yield unless @opts.profile?
37 | Template._require "memory_profiler" do
38 | profiler = MemoryProfiler.report(:top => 10_240) { yield }
39 | profiler.pretty_print({
40 | :to_file => "profile.txt"
41 | })
42 | end
43 | end
44 | end
45 | end
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/docker/template/error.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | module Error
8 | StandardError = Class.new(
9 | StandardError
10 | )
11 |
12 | # --
13 |
14 | class UnsuccessfulAuth < StandardError
15 | def initialize
16 | super "Unable to authorize you to Dockerhub, something is wrong."
17 | end
18 | end
19 |
20 | # --
21 |
22 | class BadExitStatus < StandardError
23 | attr_reader :status
24 |
25 | def initialize(status)
26 | super "Got bad exit status #{
27 | @status = status
28 | }"
29 | end
30 | end
31 |
32 | # --
33 |
34 | class BadRepoName < StandardError
35 | def initialize(name)
36 | super "Only a-z0-9_- are allowed. Invalid repo name: #{
37 | name
38 | }"
39 | end
40 | end
41 |
42 | # --
43 |
44 | class InvalidTargzFile < StandardError
45 | def initialize(tar_gz)
46 | super "No data was given to the tar.gz file '#{
47 | tar_gz.basename
48 | }'"
49 | end
50 | end
51 |
52 | # --
53 |
54 | class InvalidYAMLFile < StandardError
55 | def initialize(file)
56 | super "The yaml data provided by #{file} is invalid and not a hash."
57 | end
58 | end
59 |
60 | # --
61 |
62 | class NoSetupContext < StandardError
63 | def initialize
64 | super "No #setup_context method exists."
65 | end
66 | end
67 |
68 | # --
69 |
70 | class NotImplemented < StandardError
71 | def initialize
72 | super "The feature is not implemented yet, sorry about that."
73 | end
74 | end
75 |
76 | # --
77 |
78 | class RepoNotFound < StandardError
79 | def initialize(repo = nil)
80 | ending = repo ? "the repo '#{repo}'" : "your repo folder"
81 | super "Unable to find #{
82 | ending
83 | }"
84 | end
85 | end
86 |
87 | # --
88 |
89 | class ImageNotFound < StandardError
90 | def initialize(image)
91 | super "Unable to find the image #{
92 | image
93 | }"
94 | end
95 | end
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/lib/docker/template/logger.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 | STDOUT.sync = true
5 |
6 | module Docker
7 | module Template
8 | class Logger
9 | class << self
10 | attr_writer :stdout, :stderr
11 |
12 | # --
13 |
14 | def stdout
15 | return @stdout || $stdout
16 | end
17 |
18 | # --
19 |
20 | def stderr
21 | return @stderr || $stderr
22 | end
23 | end
24 |
25 | # --
26 |
27 | def initialize(repo = nil, stdout = nil, stderr = nil)
28 | @stdout = stdout || self.class.stdout
29 | @stderr = stderr || self.class.stderr
30 | @repo = repo
31 | @lines = {
32 | 0 => 0
33 | }
34 | end
35 |
36 | # --
37 |
38 | def output?
39 | return !!@output
40 | end
41 |
42 | # --
43 |
44 | def increment
45 | @lines.update({
46 | @lines.size => @lines.size
47 | })
48 | end
49 |
50 | # --
51 | # A simple TTY stream that just prints out the data that it is given.
52 | # This is the logger that most will use for most of their building.
53 | # --
54 |
55 | def tty(stream)
56 | @output = true
57 | @stdout.print(encode_str(
58 | stream
59 | ))
60 | end
61 |
62 | # --
63 | # A simple logger that accepts a multi-type stream.
64 | # --
65 |
66 | def simple(type, str)
67 | str = encode_str(str ||= "")
68 | type == :stderr ? @stderr.print(str) : \
69 | @stdout.print(str)
70 | end
71 |
72 | # --
73 | # A more complex streamer designed for the actual output of the Docker.
74 | # --
75 | # This method will save parts into a buffer until it can either parse
76 | # that buffer or it parses the actual part itself, if it can parse the
77 | # part itself, it will first dump the buffer as errors and then parse.
78 | # --
79 | # This method has to buffer because Docker-API (or Excon, it's depend)
80 | # gives us no indication of whether or not this is part of a larger chunk
81 | # it just dumps it on us, so we have to blindly work around that.
82 | # --
83 |
84 | def api(part, *args)
85 | part = encode_str(part).each_line.to_a
86 | if part.one? && part = part.first
87 | chunked_part = @chunks.push(part).join if @chunks && !@chunks.empty?
88 | chunked_part = part if !@chunks
89 | stream = JSON.parse(
90 | chunked_part
91 | )
92 |
93 | if chunked_part == part && @chunks && !@chunks.empty?
94 | then @chunks.each do |chunk|
95 | @stderr.puts format("Unparsable JSON: %s",
96 | chunk
97 | )
98 | end
99 | end
100 |
101 | @chunks = nil
102 | return progress_bar(stream) if stream.any_key?("progress", "progressDetail")
103 | return output(stream["status"] || stream["stream"]) if stream.any_key?("status", "stream")
104 | return progress_error(stream) if stream.any_key?("errorDetail", "error")
105 | warn Simple::Ansi.red("Unhandled Stream.")
106 | @stdout.puts(part)
107 | @output = true
108 | else
109 | part.each do |v|
110 | api(v, *args)
111 | end
112 | end
113 | # --
114 | # Sometimes we get undetectable chunks.
115 | # When we do, we try to keep them passed along.
116 | # That way we can throw them out later.
117 | # --
118 | rescue JSON::ParserError => e
119 | (@chunks ||= []).push(
120 | part
121 | )
122 | end
123 |
124 | # --
125 |
126 | def output(msg)
127 | unless filter_matches?(msg)
128 | @stdout.puts msg
129 | increment
130 | end
131 |
132 | @output = true
133 | end
134 |
135 | # --
136 |
137 | def progress_error(stream)
138 | abort Object::Simple::Ansi.red(
139 | stream["errorDetail"]["message"]
140 | )
141 | end
142 |
143 | # --
144 | # Some applications return some invalid ASCII so we need to work
145 | # around that so that no errors happen. This mostly happens
146 | # with Node.js NPM.
147 | # --
148 |
149 | private
150 | def encode_str(str)
151 | str.encode("utf-8", {
152 | :invalid => :replace, :undef => :replace, :replace => ""
153 | })
154 | end
155 |
156 | # --
157 |
158 | private
159 | def progress_bar(stream)
160 | if ENV["CI"] != "true"
161 | id = stream["id"]
162 |
163 | return unless id
164 | before, diff = progress_diff(id)
165 | @stderr.print before if before
166 | str = stream["progress"] || stream["status"]
167 | str = "#{id}: #{str}\r"
168 |
169 | @stderr.print(Object::Simple::Ansi.jump(
170 | str, diff
171 | ))
172 | end
173 | end
174 |
175 | # --
176 |
177 | private
178 | def progress_diff(id)
179 | if @lines.key?(id)
180 | return nil, @lines.size - @lines[id]
181 | end
182 |
183 | @lines[id] = @lines.size
184 | before = "\n" unless @lines.one?
185 | return before, 0
186 | end
187 |
188 | # --
189 |
190 | private
191 | def filter_matches?(msg)
192 | return false unless @repo
193 |
194 | @repo.meta["log_filters"].any? do |filter|
195 | filter.is_a?(Regexp) && msg =~ filter || msg == filter
196 | end
197 | end
198 | end
199 | end
200 | end
201 |
--------------------------------------------------------------------------------
/lib/docker/template/meta.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "active_support/inflector"
6 | require "active_support/core_ext/hash/indifferent_access"
7 | require "yaml"
8 |
9 | module Docker
10 | module Template
11 | class Meta
12 | extend Forwardable::Extended
13 | attr_reader :data
14 |
15 | # --
16 | # rubocop:disable Style/MultilineBlockLayout
17 | # --
18 |
19 | [Pathutil.allowed[:yaml][:classes], Array.allowed[:keys],
20 | Hash.allowed[:vals]].each do |v|
21 |
22 | v.push(self,
23 | HashWithIndifferentAccess, Regexp
24 | )
25 | end
26 |
27 | # --
28 | # rubocop:enable Style/MultilineBlockLayout
29 | # --
30 |
31 | DEFAULTS = HashWithIndifferentAccess.new({
32 | "squash" => false,
33 | "startup" => true,
34 | "aliases" => {},
35 | "build" => true,
36 | "cache" => false,
37 | "type" => "normal",
38 | "local_prefix" => "local",
39 | "project_data_dir" => "docker",
40 | "force" => ENV["CI"] == "true",
41 | "rootfs_base_img" => "envygeeks/alpine",
42 | "maintainer" => "Random User ",
43 | "envygeeks" => ENV["ENVYGEEKS"] && ENV["ENVYGEEKS"] == "true",
44 | "user" => ENV["USER"] || ENV["USERNAME"] || "random",
45 | "ci" => ENV["CI"] && ENV["CI"] == "true",
46 | "name" => Template.root.basename.to_s,
47 | "project_copy_dir" => "project",
48 | "rootfs_template" => "alpine",
49 | "push" => ENV["CI"] != "true",
50 | "cache_dir" => "cache",
51 | "repos_dir" => "repos",
52 | "copy_dir" => "copy",
53 | "tag" => "latest",
54 | "clean" => false,
55 | "tty" => false,
56 | "tags" => {},
57 |
58 | #
59 |
60 | "log_filters" => [
61 | /^The push refers to a repository/,
62 | /\sdigest: sha256:/
63 | ],
64 |
65 | #
66 |
67 | "project_copy_ignore" => %w(
68 | .git
69 | .bundle
70 | Dockerfile
71 | vendor/bundle
72 | .gitattributes
73 | .node_modules
74 | .gitignore
75 | docker
76 | tmp
77 | log
78 | ),
79 | }).freeze
80 |
81 | # --
82 |
83 | class << self
84 | def opts_file(force: nil)
85 | if force == :project || Template.project?
86 | then "docker/template.yml" else "opts.yml"
87 | end
88 | end
89 | end
90 |
91 | # --
92 | # @param data [Hash, self.class] - the main data.
93 | # @param root [Hash, self.class] - the root data.
94 | # Create a new instance of `self.class`.
95 | #
96 | # @example ```
97 | # self.class.new({
98 | # :hello => :world
99 | # })
100 | # ```
101 | # --
102 | # rubocop:disable Metrics/AbcSize
103 | # --
104 |
105 | def initialize(overrides, root: nil)
106 | overrides = overrides.to_h :raw => true if overrides.is_a?(self.class)
107 | root = root.to_h :raw => true if root.is_a?(self.class)
108 |
109 | if root.nil?
110 | if Template.project?
111 | load_project_config(
112 | overrides
113 | )
114 |
115 | else
116 | load_normal_config(
117 | overrides
118 | )
119 | end
120 |
121 | @root = true
122 | else
123 | @data = overrides.stringify.with_indifferent_access
124 | @root_data = root.stringify.with_indifferent_access
125 | end
126 |
127 | debug!
128 | normalize!
129 | return
130 | end
131 |
132 | # --
133 |
134 | def normalize!
135 | if root?
136 | opts = {
137 | :allowed_keys => [],
138 | :allowed_vals => []
139 | }
140 |
141 | merge!({
142 | "tags" => @data[ "tags"].stringify(**opts),
143 | "aliases" => @data["aliases"].stringify(**opts)
144 | })
145 | end
146 | end
147 |
148 | # --
149 |
150 | def debug!
151 | if root? && root_data["debug"]
152 | if !key?(:env) || self[:env].queryable?
153 | self[:env] ||= {}
154 |
155 | merge!({
156 | :env => {
157 | :all => {
158 | :DEBUG => true
159 | }
160 | }
161 | })
162 | end
163 | end
164 | end
165 |
166 | # --
167 |
168 | def root_data
169 | return @root_data || @data
170 | end
171 |
172 | # --
173 |
174 | def root
175 | if Template.project?
176 | then return Template.root.join(root_data[
177 | :project_data_dir
178 | ])
179 |
180 | else
181 | Template.root.join(
182 | root_data[:repos_dir], root_data[
183 | :name
184 | ]
185 | )
186 | end
187 | end
188 |
189 | # --
190 | # Check if a part of the hash or a value is inside.
191 | # @param val [Anytning(), Hash] - The key or key => val you wish check.
192 | # @example meta.include?(:key => :val) => true|false
193 | # @example meta.include?(:key) => true|false
194 | # --
195 |
196 | def include?(val)
197 | if val.is_a?(Hash)
198 | then val.stringify.each do |k, v|
199 | unless @data.key?(k) && @data[k] == v
200 | return false
201 | end
202 | end
203 |
204 | else
205 | return @data.include?(
206 | val
207 | )
208 | end
209 |
210 | true
211 | end
212 |
213 | # --
214 | # @param key [Anything()] the key you wish to pull.
215 | # @note we make the getter slightly more indifferent because of tags.
216 | # Pull an indifferent key from the hash.
217 | # --
218 |
219 | def [](key)
220 | val = begin
221 | if key =~ /^\d+\.\d+$/
222 | @data[key] || @data[
223 | key.to_f
224 | ]
225 |
226 | elsif key =~ /^\d+$/
227 | @data[key] || @data[
228 | key.to_i
229 | ]
230 |
231 | else
232 | @data[key]
233 | end
234 | end
235 |
236 | if val.is_a?(Hash)
237 | return self.class.new(val, {
238 | :root => root_data
239 | })
240 | end
241 |
242 | val
243 | end
244 |
245 | # --
246 |
247 | def []=(key, val)
248 | hash = { key => val }.stringify
249 | @data.update(
250 | hash
251 | )
252 | end
253 |
254 | # --
255 |
256 | def update(hash)
257 | @data.update(
258 | hash.stringify
259 | )
260 | end
261 |
262 | # --
263 |
264 | def to_enum
265 | @data.each_with_object({}) do |(k, v), h|
266 | if v.is_a?(Hash)
267 | then v = self.class.new(v, {
268 | :root => root_data
269 | })
270 | end
271 |
272 | h[k] = v
273 | end.to_enum
274 | end
275 |
276 | # --
277 | # Merge a hash into the meta. If you merge non-queryable data
278 | # it will then get merged into the queryable data.
279 | # --
280 |
281 | def merge(new_)
282 | if !queryable?(:query_data => new_) && queryable?
283 | new_ = {
284 | :all => new_
285 | }
286 | end
287 |
288 | new_ = new_.stringify
289 | self.class.new(@data.deep_merge(new_), {
290 | :root => root_data
291 | })
292 | end
293 |
294 | # --
295 | # Destructive merging (@see self#merge)
296 | # --
297 |
298 | def merge!(new_)
299 | if !queryable?(:query_data => new_) && queryable?
300 | new_ = {
301 | :all => new_
302 | }
303 | end
304 |
305 | @data = @data.deep_merge(
306 | new_.stringify
307 | )
308 |
309 | self
310 | end
311 |
312 | # --
313 | # Check if a hash is queryable. AKA has "all", "group", "tag".
314 | # --
315 |
316 | def queryable?(query_data: @data)
317 | if query_data.is_a?(self.class)
318 | then query_data
319 | .queryable?
320 |
321 | elsif !query_data || !query_data.is_a?(Hash) || query_data.empty?
322 | return false
323 |
324 | else
325 | (query_data.keys - %w(
326 | group tag all
327 | )).empty?
328 | end
329 | end
330 |
331 | # --
332 | # Fallback, determining which route is the best. Tag > Group > All.
333 | # --
334 | # rubocop:disable Metrics/CyclomaticComplexity
335 | # rubocop:disable Metrics/PerceivedComplexity
336 | # --
337 |
338 | def fallback(group: current_group, tag: current_tag, query_data: @data)
339 | if query_data.is_a?(self.class)
340 | then query_data.fallback({
341 | :group => group, :tag => tag
342 | })
343 |
344 | elsif !query_data || !query_data.is_a?(Hash) || query_data.empty?
345 | return nil
346 |
347 | else
348 | if !(v = by_tag(:tag => tag, :query_data => query_data)).nil? then return v
349 | elsif !(v = by_parent_tag(:tag => tag, :query_data => query_data)).nil? then return v
350 | elsif !(v = by_group(:group => group, :query_data => query_data)).nil? then return v
351 | elsif !(v = by_parent_group(:tag => tag, :query_data => query_data)).nil? then return v
352 | else return for_all(:query_data => query_data)
353 | end
354 | end
355 | end
356 |
357 | # --
358 | # rubocop:enable Metrics/CyclomaticComplexity
359 | # rubocop:enable Metrics/PerceivedComplexity
360 | # --
361 |
362 | def for_all(query_data: @data)
363 | if query_data.is_a?(self.class)
364 | then query_data \
365 | .for_all
366 |
367 | elsif !query_data || !query_data.is_a?(Hash)
368 | return nil
369 |
370 | else
371 | query_data.fetch(
372 | "all", nil
373 | )
374 | end
375 | end
376 |
377 | # --
378 |
379 | def by_tag(tag: current_tag, query_data: @data)
380 | if query_data.is_a?(self.class)
381 | then query_data.by_tag({
382 | :tag => tag
383 | })
384 |
385 | elsif !query_data || !query_data.is_a?(Hash)
386 | return nil
387 |
388 | else
389 | query_data.fetch("tag", {}).fetch(
390 | tag, nil
391 | )
392 | end
393 | end
394 |
395 | # --
396 |
397 | def by_parent_tag(tag: current_tag, query_data: @data)
398 | if aliased_tag == current_tag || !complex_alias?
399 | return nil
400 |
401 | else
402 | by_tag({
403 | :query_data => query_data,
404 | :tag => aliased_tag({
405 | :tag => tag
406 | })
407 | })
408 | end
409 | end
410 |
411 | # --
412 |
413 | def by_group(group: current_group, query_data: @data)
414 | if query_data.is_a?(self.class)
415 | then query_data.by_group({
416 | :group => group
417 | })
418 |
419 | elsif !query_data || !query_data.is_a?(Hash)
420 | return nil
421 |
422 | else
423 | query_data.fetch("group", {}).fetch(
424 | group, nil
425 | )
426 | end
427 | end
428 |
429 | # --
430 |
431 | def by_parent_group(tag: current_tag, query_data: @data)
432 | if aliased_tag == current_tag || !complex_alias?
433 | return nil
434 |
435 | else
436 | by_group({
437 | :query_data => query_data,
438 | :group => aliased_group({
439 | :tag => tag
440 | })
441 | })
442 | end
443 | end
444 |
445 | # --
446 | # Checks to see if the current meta is an alias of another. This
447 | # happens when the user has the tag in aliases but it's not complex.
448 | # --
449 |
450 | def alias?
451 | !!(aliased_tag && aliased_tag != tag)
452 | end
453 |
454 | # --
455 | # A complex alias happens when the user has an alias but also tries to
456 | # add extra data, this allows them to use data from all parties. This
457 | # allows them to reap the benefits of having shared data but sometimes
458 | # independent data that diverges into it's own.
459 | # --
460 |
461 | def complex_alias?
462 | if !alias?
463 | return false
464 |
465 | else
466 | !!root_data.find do |_, v|
467 | (v.is_a?(self.class) || v.is_a?(Hash)) && queryable?(:query_data => v) \
468 | && by_tag(:query_data => v)
469 | end
470 | end
471 | end
472 |
473 | # --
474 |
475 | def aliased_tag(tag: current_tag)
476 | aliases = root_data[:aliases]
477 | if aliases.nil? || !aliases.key?(tag)
478 | tag
479 |
480 | else
481 | aliases[
482 | tag
483 | ]
484 | end
485 | end
486 |
487 | # --
488 |
489 | def aliased_group(tag: current_tag)
490 | root_data[:tags][aliased_tag({
491 | :tag => tag
492 | })]
493 | end
494 |
495 | # --
496 | # Converts the current meta into a string.
497 | # --
498 |
499 | def to_s(raw: false, shell: false)
500 | if !raw && (mergeable_hash? || mergeable_array?)
501 | to_a(:shell => shell).join(" #{
502 | "\n" if shell
503 | }")
504 |
505 | elsif !raw && queryable?
506 | then fallback \
507 | .to_s
508 |
509 | else
510 | @data.to_s
511 | end
512 | end
513 |
514 | # --
515 | # rubocop:disable Metrics/CyclomaticComplexity
516 | # rubocop:disable Metrics/PerceivedComplexity
517 | # --
518 |
519 | def to_a(raw: false, shell: false)
520 | if raw
521 | return to_h({
522 | :raw => true
523 | }).to_a
524 |
525 | elsif !mergeable_array?
526 | to_h.each_with_object([]) do |(k, v), a|
527 | a << "#{k}=#{
528 | shell ? v.to_s.shellescape : v
529 | }"
530 | end
531 | else
532 | (for_all || []) | (by_parent_group || []) | (by_group || []) | \
533 | (by_parent_tag || []) | (by_tag || [])
534 | end
535 | end
536 |
537 | # --
538 | # rubocop:eanble Metrics/CyclomaticComplexity
539 | # rubocop:eanble Metrics/PerceivedComplexity
540 | # --
541 | # Convert a `Meta' into a normal hash. If `self' is queryable then
542 | # we go and start merging values smartly. This means that we will merge
543 | # all the arrays into one another and we will merge hashes into hashes.
544 | # --
545 | # rubocop:disable Metrics/AbcSize
546 | # --
547 |
548 | def to_h(raw: false)
549 | return @data.to_h if raw || !queryable? || !mergeable_hash?
550 | keys = [for_all, by_group, by_parent_group, by_tag, \
551 | by_parent_tag].compact.map(&:keys)
552 |
553 | keys.reduce(:+).each_with_object({}) do |k, h|
554 | vals = [for_all, by_group, by_parent_group, by_tag, \
555 | by_parent_tag].compact
556 |
557 | h[k] = \
558 | if mergeable_array?(k)
559 | vals.map { |v| v[k].to_a } \
560 | .compact.reduce(
561 | :+
562 | )
563 |
564 | elsif mergeable_hash?(k)
565 | vals.map { |v| v[k].to_h } \
566 | .compact.reduce(
567 | :deep_merge
568 | )
569 |
570 | else
571 | vals.find do |v|
572 | v.key?(
573 | k
574 | )
575 | end \
576 | [k]
577 | end
578 | end
579 | end
580 |
581 | # --
582 | # rubocop:enable Metrics/AbcSize
583 | # --
584 |
585 | def mergeable_hash?(key = nil)
586 | return false unless queryable?
587 | vals = [by_parent_tag, by_parent_group, \
588 | by_tag, for_all, by_group].compact
589 |
590 | if key
591 | vals = vals.map do |val|
592 | val[key]
593 | end
594 | end
595 |
596 | !vals.empty? && !vals.any? do |val|
597 | !val.is_a?(Hash) && !val.is_a?(
598 | self.class
599 | )
600 | end
601 | end
602 |
603 | # --
604 |
605 | def mergeable_array?(key = nil)
606 | return false unless queryable?
607 | vals = [by_parent_tag, by_parent_group, \
608 | by_tag, for_all, by_group].compact
609 |
610 | if key
611 | vals = vals.map do |val|
612 | val[key]
613 | end
614 | end
615 |
616 | !vals.empty? && !vals.any? do |val|
617 | !val.is_a?(
618 | Array
619 | )
620 | end
621 | end
622 |
623 | # --
624 |
625 | def current_group
626 | root_data[:tags][current_tag] ||
627 | "normal"
628 | end
629 |
630 | # --
631 | # HELPER: Get a list of all the tags.
632 | # --
633 |
634 | def tags
635 | (root_data[:tags] || {}).keys | (root_data[:aliases] || {}).keys
636 | end
637 |
638 | # --
639 | # HELPER: Get a list of all the groups.
640 | # --
641 |
642 | def groups
643 | root_data["tags"].values.uniq
644 | end
645 |
646 | # --
647 |
648 | private
649 | def merge_or_override(val, new_val)
650 | return new_val unless val
651 | return val if val.is_a?(String) && !new_val || !new_val.is_a?(val.class)
652 | return new_val.merge(val) if val.respond_to?(:merge)
653 | return new_val | val if val.respond_to?(:|)
654 | end
655 |
656 | # --
657 |
658 | private
659 | def string_wrapper(obj, shell: false)
660 | return obj if obj == true || obj == false || obj.nil?
661 | return obj.fallback if obj.is_a?(self.class) && obj.queryable? \
662 | && !(o = obj.fallback).nil? && (o == true || o == false)
663 |
664 | return obj.to_s(:shell => shell) if obj.is_a?(self.class)
665 | !obj.is_a?(Array) ? obj.to_s : obj.join(
666 | "\s"
667 | )
668 | end
669 |
670 | # --
671 |
672 | private
673 | def method_missing(method, *args, shell: false, &block)
674 | key = method.to_s.gsub(/\?$/, "")
675 | val = self[key] || self[key.singularize] \
676 | || self[key.pluralize]
677 |
678 | if !args.empty? || block_given?
679 | super
680 |
681 | elsif method !~ /\?$/
682 | string_wrapper(val, {
683 | :shell => shell
684 | })
685 |
686 | else
687 | val = val.fallback if val.is_a?(self.class) && val.queryable?
688 | [true, false].include?(val) ? val : \
689 | if val.respond_to?(:empty?)
690 | then !val.empty? else !!val
691 | end
692 | end
693 | end
694 |
695 | # --
696 |
697 | private
698 | def load_normal_config(overrides)
699 | overrides = overrides.stringify
700 | gdata = Template.root.join(self.class.opts_file).read_yaml
701 | @data = DEFAULTS.deep_merge(gdata.stringify).deep_merge(overrides)
702 | tdata = Template.root.join(@data[:repos_dir], @data[:name], self.class.opts_file).read_yaml
703 | @data = @data.deep_merge(tdata.stringify).deep_merge(overrides)
704 | @data = @data.stringify.with_indifferent_access
705 | end
706 |
707 | # --
708 |
709 | private
710 | def load_project_config(overrides)
711 | overrides = overrides.stringify
712 | gdata = Template.root.join(self.class.opts_file).read_yaml
713 | @data = DEFAULTS.deep_merge(gdata.stringify).deep_merge(overrides)
714 | @data = @data.stringify.with_indifferent_access
715 | end
716 |
717 | # --
718 |
719 | alias deep_merge merge
720 | alias group current_group
721 | rb_delegate :for_all, :to => :self, :type => :hash, :key => :all
722 | rb_delegate :current_tag, :to => :root_data, :key => :tag, :type => :hash
723 | rb_delegate :tag, :to => :root_data, :type => :hash, :key => :tag
724 | rb_delegate :root, :to => :@root, :type => :ivar, :bool => true
725 |
726 | # --
727 |
728 | rb_delegate :fetch, :to => :@data
729 | rb_delegate :delete, :to => :@data
730 | rb_delegate :empty?, :to => :@data
731 | rb_delegate :inspect, :to => :@data
732 | rb_delegate :values_at, :to => :@data
733 | rb_delegate :values, :to => :@data
734 | rb_delegate :keys, :to => :@data
735 | rb_delegate :key?, :to => :@data
736 | rb_delegate :==, :to => :@data
737 |
738 | # --
739 |
740 | rb_delegate :inject, :to => :to_enum
741 | rb_delegate :select, :to => :to_enum
742 | rb_delegate :each_with_object, :to => :to_enum
743 | rb_delegate :collect, :to => :to_enum
744 | rb_delegate :find, :to => :to_enum
745 | rb_delegate :each, :to => :to_enum
746 | end
747 | end
748 | end
749 |
--------------------------------------------------------------------------------
/lib/docker/template/notify.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | module Notify
8 | module_function
9 |
10 | # --
11 | # Notify the user of a push that is happening.
12 | # --
13 |
14 | def push(builder)
15 | $stderr.puts Simple::Ansi.green(
16 | "Pushing: #{builder.repo}"
17 | )
18 | end
19 |
20 | # --
21 | # Notify the user that we are tag aliasing.
22 | # --
23 |
24 | def alias(builder)
25 | repo = builder.repo
26 | aliased_repo = builder.aliased_repo || builder.aliased_tag
27 | msg = Simple::Ansi.green("Aliasing #{repo} -> #{aliased_repo}")
28 | $stderr.puts msg
29 | end
30 |
31 | # --
32 |
33 | def build(repo, rootfs: false)
34 | build_start(repo, {
35 | :rootfs => rootfs
36 | })
37 |
38 | if block_given?
39 | yield
40 | build_end(repo, {
41 | :rootfs => rootfs
42 | })
43 | end
44 | end
45 |
46 | # --
47 | # Notify the user that we are building their repository.
48 | # --
49 |
50 | def build_start(repo, rootfs: false)
51 | if ENV["TRAVIS"] && !ENV.key?("RSPEC_RUNNING")
52 | STDOUT.puts(format("travis_fold:end:%s",
53 | repo.to_s(:rootfs => rootfs).tr("^A-Za-z0-9", "-").gsub(
54 | /\-$/, ""
55 | )
56 | ))
57 | end
58 |
59 | $stderr.puts Simple::Ansi.green(format(
60 | "Building: %s", repo.to_s({
61 | :rootfs => rootfs
62 | })
63 | ))
64 | end
65 |
66 | # --
67 | # Notify the user that building their repository has ended.
68 | # --
69 |
70 | def build_end(repo, rootfs: false)
71 | if ENV["TRAVIS"] && !ENV.key?("RSPEC_RUNNING")
72 | STDOUT.puts(format("travis_fold:end:%s",
73 | repo.to_s(:rootfs => rootfs).tr("^A-Za-z0-9", "-").gsub(
74 | /\-$/, ""
75 | )
76 | ))
77 | end
78 |
79 | $stderr.puts Simple::Ansi.green(format(
80 | "Done Building: %s", repo.to_s({
81 | :rootfs => rootfs
82 | })
83 | ))
84 | end
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/lib/docker/template/parser.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class Parser
8 | SLASH_REGEXP = /\//
9 | SPLIT_REGEXP = /:|\//
10 | COLON_REGEXP = /:/
11 |
12 | def initialize(raw_repos = [], argv = {})
13 | @raw_repos = raw_repos
14 | @argv = argv
15 | end
16 |
17 | # --
18 | # Return `raw_repos` if you send us a list of repos you wish to build,
19 | # otherwise we get the children of the repo folder and ship that off so
20 | # you can build *every* repo, I don't know if you want that.
21 | # --
22 | def all
23 | return @raw_repos unless @raw_repos.empty?
24 | return [Template.root.basename.to_s] if Template.project?
25 | Template.root.join(Meta.new({}).repos_dir).children.map do |path|
26 | path.basename.to_s
27 | end
28 |
29 | rescue Errno::ENOENT
30 | then raise(
31 | Error::RepoNotFound
32 | )
33 | end
34 |
35 | # --
36 | # rubocop:disable Metrics/AbcSize
37 | # --
38 | def parse
39 | scratch = []
40 | simple = []
41 | aliases = []
42 |
43 | all.each do |v|
44 | hash = self.class.to_repo_hash(v)
45 | raise Error::BadRepoName, v if hash.empty?
46 | Repo.new(hash, @argv).to_repos.each do |r|
47 | scratch << r if r.builder.scratch? && !r.alias?
48 | simple << r unless r.alias? || r.builder.scratch?
49 | aliases << r if r.alias?
50 | end
51 | end
52 |
53 | out = aliases.each_with_object(scratch | simple) do |alias_, repos|
54 | index = repos.rindex { |v| v.name == alias_.name }
55 | if index
56 | repos.insert(index + 1,
57 | alias_
58 | )
59 |
60 | else
61 | repos.push(
62 | alias_
63 | )
64 | end
65 | end
66 | end
67 |
68 | # --
69 | # rubocop:enable Metrics/AbcSize
70 | # --
71 | def self.to_repo_hash(val)
72 | data = val.to_s.split(SPLIT_REGEXP)
73 |
74 | return "name" => data[0] if data.one?
75 | return "name" => data[0], "tag" => data[1] if val =~ COLON_REGEXP && data.size == 2
76 | return "user" => data[0], "name" => data[1] if val =~ SLASH_REGEXP && data.size == 2
77 | return "user" => data[0], "name" => data[1], "tag" => data[2] if data.size == 3
78 |
79 | {}
80 | end
81 |
82 | # --
83 |
84 | def self.from_tag_to_repo_hash(val)
85 | data = val.to_s.split(SPLIT_REGEXP)
86 |
87 | return "tag" => data[0] if data.one?
88 | return "name" => data[0], "tag" => data[1] if val =~ COLON_REGEXP && data.size == 2
89 | return "user" => data[0], "name" => data[1] if val =~ SLASH_REGEXP && data.size == 2
90 | return "user" => data[0], "name" => data[1], "tag" => data[2] if data.size == 3
91 |
92 | {}
93 | end
94 |
95 | # --
96 |
97 | def self.full_name?(val)
98 | parsed = to_repo_hash(val)
99 | parsed.key?("name") && (parsed.key?("user") || parsed.key?(
100 | "tag"
101 | ))
102 | end
103 | end
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/lib/docker/template/repo.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | class Repo
8 | extend Forwardable::Extended
9 |
10 | # --
11 |
12 | def initialize(*hashes)
13 | @base_meta = hashes.compact
14 | @base_meta = @base_meta.reduce(:deep_merge)
15 | @base_meta.freeze
16 |
17 | unless root.exist?
18 | raise(
19 | Error::RepoNotFound, name
20 | )
21 | end
22 | end
23 |
24 | # --
25 |
26 | def pushable?
27 | (meta["push"] || meta["push_only"]) &&
28 | !meta["cache_only"] && !meta[
29 | "clean_only"
30 | ]
31 | end
32 |
33 | # --
34 |
35 | def cacheable?
36 | (meta["cache"] || meta["cache_only"]) &&
37 | !meta[
38 | "push_only"
39 | ]
40 | end
41 |
42 | # --
43 |
44 | def clean_cache?
45 | (meta["clean"] || meta["clean_only"])
46 | end
47 |
48 | # --
49 |
50 | def buildable?
51 | meta.build? && !meta["push_only"] && !meta["cache_only"] &&
52 | !meta[
53 | "clean_only"
54 | ]
55 | end
56 |
57 | # --
58 | # Pulls out the repo this repo is aliasing it, this happens when you
59 | # when you set the tag in the "alias" section of your `opts.yml`.
60 | # --
61 | def aliased
62 | full = Parser.full_name?(
63 | meta.aliased_tag
64 | )
65 |
66 | if alias? && full
67 | self.class.new(to_h.merge(Parser.to_repo_hash(
68 | meta.aliased_tag
69 | )))
70 |
71 | elsif alias?
72 | self.class.new(to_h.merge({
73 | "tag" => meta.aliased_tag
74 | }))
75 | end
76 |
77 | rescue Error::RepoNotFound => e
78 | unless full
79 | raise e
80 | end
81 | end
82 |
83 | # --
84 | # Initializes and returns the builder so that you can build the repo.
85 | # --
86 | def builder
87 | return @builder ||= begin
88 | Template::Builder.const_get(type.capitalize).new(
89 | self
90 | )
91 | end
92 | end
93 |
94 | # --
95 | # Convert the repo into it's final image name, however if you tell, us
96 | # this is a rootfs build we will convert it into the rootfs name.
97 | # --
98 | def to_s(rootfs: false)
99 | prefix = meta["local_prefix"]
100 | return "#{user}/#{name}:#{tag}" unless rootfs
101 | "#{prefix}/rootfs:#{name}"
102 | end
103 |
104 | # --
105 | # The directory you wish to cache to (like `cache/`) or other.
106 | # --
107 | def cache_dir
108 | return root.join(
109 | meta["cache_dir"], tag
110 | )
111 | end
112 |
113 | # --
114 | # The directory you store your image data in (by default `copy/`.)
115 | # --
116 | def copy_dir(*path)
117 | dir = meta["copy_dir"]
118 | root.join(
119 | dir, *path
120 | )
121 | end
122 |
123 | # --
124 |
125 | def to_tag_h
126 | {
127 | "tag" => tag,
128 | "repo" => "#{user}/#{name}",
129 | "force" => true
130 | }
131 | end
132 |
133 | # --
134 |
135 | def to_rootfs_h
136 | {
137 | "tag" => name,
138 | "repo" => "#{meta["local_prefix"]}/rootfs",
139 | "force" => true
140 | }
141 | end
142 |
143 | # --
144 |
145 | def tmpdir(*args, root: Template.tmpdir)
146 | args.unshift(user.gsub(/[^A-Za-z0-9_\-]+/, "--"), name, tag)
147 | out = Pathutil.tmpdir(args, nil, root)
148 | out.realpath
149 | end
150 |
151 | # --
152 |
153 | def tmpfile(*args, root: Template.tmpdir)
154 | args.unshift(user, name, tag)
155 | Pathutil.tmpfile(
156 | args, nil, root
157 | )
158 | end
159 |
160 | # --
161 | # If a tag was given then it returns [self] and if a tag was not sent
162 | # it then goes on to detect the type and split itself accordingly
163 | # returning multiple, AKA all repos that should be built.
164 | # --
165 | def to_repos
166 | if Template.project?
167 | then Set.new([
168 | self
169 | ])
170 |
171 | else
172 | set = Set.new
173 | if @base_meta.key?("tag")
174 | set << self
175 | else
176 | tags.each do |tag|
177 | hash = Parser.from_tag_to_repo_hash(tag)
178 | hash = to_h.merge(hash)
179 | set << self.class.new(
180 | hash, @cli_opts
181 | )
182 | end
183 | end
184 |
185 | set
186 | end
187 | end
188 |
189 | # --
190 |
191 | def meta
192 | return @meta ||= begin
193 | Meta.new(
194 | @base_meta
195 | )
196 | end
197 | end
198 |
199 | # --
200 |
201 | def to_env(tar_gz: nil, copy_dir: nil)
202 | hash = meta["env"] || { "all" => {}}
203 | Meta.new(hash, :root => meta).merge({
204 | "REPO" => name,
205 | "TAR_GZ" => tar_gz,
206 | "GROUP" => meta.group,
207 | "DEBUG" => meta.debug?? "true" : "",
208 | "COPY_DIR" => copy_dir,
209 | "BUILD_TYPE" => type,
210 | "TAG" => tag
211 | })
212 | end
213 |
214 | # --
215 |
216 | rb_delegate :build, :to => :builder
217 | rb_delegate :alias?, :to => :meta
218 | rb_delegate :complex_alias?, :to => :meta
219 | rb_delegate :type, :to => :meta, :type => :hash
220 | rb_delegate :user, :to => :meta, :type => :hash
221 | rb_delegate :name, :to => :meta, :type => :hash
222 | rb_delegate :tag, :to => :meta, :type => :hash
223 | rb_delegate :to_h, :to => :@base_meta
224 | rb_delegate :root, :to => :meta
225 | rb_delegate :tags, :to => :meta
226 | rb_delegate :clean, {
227 | :to => Cache, :alias_of => :cleanup, :args => %w(
228 | self
229 | )
230 | }
231 | end
232 | end
233 | end
234 |
--------------------------------------------------------------------------------
/lib/docker/template/version.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Docker
6 | module Template
7 | VERSION = "0.22.0"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/erb/context.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "erb"
6 | class ERB
7 | class Context
8 |
9 | # --
10 | # Wraps any data you wish to send to ERB limiting it's context access.
11 | # @param [Hash] vars the variables you wish to set
12 | # --
13 | def initialize(vars)
14 | vars.each do |key, val|
15 | instance_variable_set("@#{key}", val)
16 | end
17 | end
18 |
19 | # --
20 | # Returns the binding so that we can ship it off and give it to ERB.
21 | # --
22 | def _binding
23 | return binding
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/script/install:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright: 2017 - 2018 - MIT License
3 | # Source: https://github.com/envygeeks/devfiles
4 | # Author: Jordon Bedwell
5 | [ "$DEBUG" = "true" ] && set -x
6 | set -e
7 |
8 | f=script/script.d/install
9 | [ "$SKIP_SCRIPTD" != "true" ] && [ -x $f ] && exec $f "$@"
10 | if bundle check 1>/dev/null 2>&1; then
11 | bundle update
12 | else
13 | bundle install --path \
14 | vendor/bundle
15 | fi
16 |
17 | bundle clean
18 | if [ -f package.json ]; then
19 | yarn
20 | fi
21 |
--------------------------------------------------------------------------------
/script/rake.d/update.rake:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | $LOAD_PATH.unshift(File.expand_path(
6 | "../lib", __FILE__
7 | ))
8 |
9 | # --
10 |
11 | require "simple/ansi"
12 | require "docker/template/cli"
13 | require "open3"
14 |
15 | # --
16 | # TODO: Cleanup and remove this whenever you can.
17 | # --
18 | module CompList
19 | module_function
20 |
21 | # --
22 | # Update the pak file to have all the completions.
23 | # --
24 | def update(data = get_commands, msgp = data.to_msgpack)
25 | pak_file.binwrite(
26 | msgp
27 | )
28 | end
29 |
30 | # --
31 |
32 | def normalize_command(command)
33 | if command.is_a?(Array)
34 | then command.map do |key|
35 | key.tr(
36 | "_", "-"
37 | )
38 | end
39 | else
40 | command.tr(
41 | "_", "-"
42 | )
43 | end
44 | end
45 |
46 | # --
47 | # Provides the base "_reply" for your auto-complete data output.
48 | # --
49 | def base(const, skip = %w(help))
50 | keys = const.all_commands.keys
51 | return "_reply" => normalize_command(keys), "help" => {
52 | "_reply" => normalize_command(keys) - skip
53 | }
54 | end
55 |
56 | # --
57 |
58 | def add_opts(out, const)
59 | const.all_commands.each do |key, val, command = normalize_command(key)|
60 | val.options.map do |_, opt|
61 | out[command] ||= { "_reply" => [] }
62 | ary = out[command][
63 | "_reply"
64 | ]
65 |
66 | if !opt.boolean?
67 | ary << "#{
68 | opt.switch_name
69 | }="
70 |
71 | else
72 | ary << opt.switch_name
73 | ary << "--no-#{opt.switch_name.gsub(
74 | /\A--/, ""
75 | )}"
76 | end
77 |
78 | ary | opt.aliases
79 | end
80 | end
81 |
82 | out
83 | end
84 |
85 | # --
86 | # Recursively pulls out and set's up your commands and opts.
87 | # --
88 | def get_commands(const = Docker::Template::CLI)
89 | out = base(
90 | const
91 | )
92 |
93 | const.subcommands.each do |key, command = normalize_command(key)|
94 | const_list = const.to_namespace.push(command.to_namespace)
95 | out[command] = send(__method__, Thor::Namespace.resolv(
96 | const_list
97 | ))
98 | end
99 |
100 | add_opts(
101 | out, const
102 | )
103 | end
104 |
105 | # --
106 |
107 | def pak_file
108 | Pathutil.new("comp/list.pak").expand_path.tap(
109 | &:touch
110 | )
111 | end
112 | end
113 |
114 | # --
115 |
116 | namespace :update do
117 | desc "Update the completion list."
118 | task "comp-list" do
119 | require "msgpack"
120 | require "docker/template"
121 | CompList.update
122 | end
123 | end
124 |
--------------------------------------------------------------------------------
/script/report:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright: 2017 - 2018 - MIT License
3 | # Source: https://github.com/envygeeks/devfiles
4 | # Author: Jordon Bedwell
5 | [ "$DEBUG" = "true" ] && set -x
6 | set -e
7 |
8 | f=script/script.d/report
9 | [ "$SKIP_SCRIPTD" != "true" ] && [ -x $f ] && exec $f "$@"
10 | if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "$TRAVIS" = "true" ]; then
11 | if [ "$1" = "before" ]; then
12 | # TODO: Start tracking the SHA sum, this is crazy..
13 | url=https://codeclimate.com/downloads/test-reporter
14 | url=$url/test-reporter-latest-linux-amd64
15 | curl -sL $url > ./cctr
16 |
17 | # --
18 | # Eventually they should make this the default on Travis-CI
19 | # this way I don't worry about some strange stuff going on with
20 | # a possible compromise, and an invalid SHA.
21 | # --
22 |
23 | chmod +x ./cctr && ./cctr before-build
24 | else
25 | ./cctr after-build --exit-code \
26 | $TRAVIS_TEST_RESULT
27 | fi
28 | fi
29 |
--------------------------------------------------------------------------------
/script/sync:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright: 2017 - 2018 - MIT License
3 | # Source: https://github.com/envygeeks/devfiles
4 | # Author: Jordon Bedwell
5 | # --
6 |
7 | [ "$DEBUG" = "true" ] && set -x
8 | set -e
9 |
10 | # --
11 | trim() {
12 | echo "$@" | tr -s " " | \
13 | sed -e 's/^[[:space:]]*//g' | \
14 | sed -e 's/[[:space:]]$//g'
15 | }
16 |
17 | # --
18 | [ -n "$1" ] && git config envygeeks.language "$1"
19 | [ -n "$2" ] && git config envygeeks.external "$2"
20 |
21 | # --
22 | # Determine what we are working with.
23 | # --
24 |
25 | skip_if_exists=(Gemfile .travis.yml)
26 | sync_language=$(git config envygeeks.language || echo "")
27 | skip_if_external=(CONTRIBUTING.md CODE_OF_CONDUCT.md .github/codeowners)
28 | skip_if_external+=(.github/issue_template.md .github/pull_request_template.md)
29 | external=$(git config envygeeks.external || echo "false")
30 | github_url=https://github.com/envygeeks/devfiles.git
31 | copy_to=$PWD
32 |
33 | # --
34 | # Copies everything, except for what's to be skipped if
35 | # it happens to exist, into the current directory, so that
36 | # stays in sync with the parent.
37 | # --
38 |
39 | clone_dir=$(mktemp -d)
40 | git clone $github_url "$clone_dir"
41 | for d in $(trim global $sync_language); do
42 | cd "$clone_dir"
43 |
44 | [ ! -d "$d" ] && continue
45 | find "$d" -not -path "$d" -type f | while read f; do
46 | to=$(echo "$f" | sed -e "s/$d\///"); dir=$(dirname "$to")
47 | if [ "$dir" != "$d" ] && [ "$dir" != "." ]; then
48 | if [ ! -d "$copy_to/$dir" ]; then
49 | bash -xc "mkdir -p '$copy_to/$dir'"
50 | fi
51 | fi
52 |
53 | skip=false
54 | for sf in "${skip_if_exists[@]}"; do
55 | if [ "$to" = "$sf" ] && [ -f "$copy_to/$sf" ]; then
56 | skip=true
57 | fi
58 | done
59 |
60 | if [ "$skip" = "false" ] && [ "$external" = "true" ]; then
61 | for sf in "${skip_if_external[@]}"; do
62 | if [ "$to" = "$sf" ] && [ -f "$copy_to/$sf" ]; then
63 | skip=true
64 | fi
65 | done
66 | fi
67 |
68 | if [ "$skip" != "true" ]; then
69 | bash -xc "cp -L '$f' '$copy_to/$to'"
70 | fi
71 | done
72 | done
73 |
74 | rm -rf $clone_dir
75 |
--------------------------------------------------------------------------------
/script/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright: 2017 - 2018 - MIT License
3 | # Source: https://github.com/envygeeks/devfiles
4 | # Author: Jordon Bedwell
5 | [ "$DEBUG" = "true" ] && set -x
6 | set -e
7 |
8 | # --
9 | hasGem() {
10 | if cat Gemfile | grep -qEi "^\s*gem\s+(\"|')$1(\\1)"; then
11 | return 0
12 | else
13 | find . -maxdepth 1 -type f -name \*.gemspec | while read f; do
14 | if cat "$f" | grep -qEi "(\"|')$1(\\1)"; then
15 | return 1
16 | fi
17 | done && return 1 || return 0
18 | fi
19 |
20 | return 1
21 | }
22 |
23 | # --
24 | # This logic exists so that on CI's all you need do is
25 | # set an environment variable to kick on linting, without
26 | # having to do excessive customization of the CI.
27 | # --
28 | f=script/script.d/test
29 | [ "$SKIP_SCRIPTD" != "true" ] && [ -x $f ] && exec $f "$@"
30 | hasGem "minitest" && [ $# -gt 0 ] && exec ruby -rminitest/autorun "$@"
31 | hasGem "minitest" && exec ruby -rminitest/autorun test/**/*_{test,spec}.rb
32 | hasGem "rspec" && exec bundle exec rspec "$@"
33 | hasGem "rspec-rails" && exec bundle exec rspec "$@"
34 | [ -x bin/rails ] && exec bin/rails test "$@"
35 | [ $# -gt 0 ] && exec ruby -Itest "$@"
36 | exec ruby -Itest test/**/*_test.rb
37 |
--------------------------------------------------------------------------------
/spec/rspec/helper.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | ENV.delete("CI")
6 | require "support/coverage"
7 | require "luna/rspec/formatters/checks"
8 | require "docker/template"
9 | require "rspec/helpers"
10 |
11 | ENV["RSPEC_RUNNING"] ||= "true"
12 | Dir[File.expand_path("../../support/**/*.rb", __FILE__)].each do |f|
13 | require f
14 | end
15 |
--------------------------------------------------------------------------------
/spec/support/clear.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | RSpec.configure do |config|
6 | config.before do |ex|
7 | unless ex.metadata[:clear]
8 | allow(Simple::Ansi).to receive(:clear).and_return(
9 | nil
10 | )
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/color.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | RSpec.configure do |config|
6 | config.color = true
7 | end
8 |
--------------------------------------------------------------------------------
/spec/support/contexts/docker.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | shared_context :docker do
6 | before do
7 | allow(Docker::Container).to receive(:create) do
8 | container_mock
9 | end
10 | end
11 |
12 | #
13 |
14 | let :container_mock do
15 | Mocks::Container \
16 | .new
17 | end
18 |
19 | #
20 |
21 | before do
22 | allow(Docker::Image).to receive(:build_from_dir) do
23 | image_mock
24 | end
25 | end
26 |
27 | #
28 |
29 | let :image_mock do
30 | Mocks::Image \
31 | .new
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/spec/support/contexts/repos.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | shared_context :repos do
6 | before do |ex|
7 | mocked_repo.setup
8 | unless ex.metadata[:init] == false
9 | mocked_repo.init({
10 | :type => ex.metadata[:type] ||= :scratch
11 | })
12 | end
13 | end
14 |
15 | #
16 |
17 | let :mocked_repo do
18 | Mocks::Repo.new(
19 | self
20 | )
21 | end
22 |
23 | #
24 |
25 | after do
26 | mocked_repo.teardown
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/spec/support/coverage.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "simplecov"
6 | SimpleCov \
7 | .start
8 |
--------------------------------------------------------------------------------
/spec/support/filter.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | RSpec.configure do |config|
6 | config.filter_run_excluding :ruby => (lambda do |type|
7 | RUBY_ENGINE == begin
8 | type == "!mri" || type == "!ruby" ? "ruby" : \
9 | if type == "!jruby"
10 | "jruby"
11 | end
12 | end
13 | end)
14 | end
15 |
--------------------------------------------------------------------------------
/spec/support/matchers/have_const.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | RSpec::Matchers.define :have_const do |const|
6 | match do |owner|
7 | owner.const_defined?(const)
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/support/mocks/container.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Mocks
6 | class Container
7 | attr_reader :struct
8 | def initialize
9 | @mocked = [
10 | :delete,
11 | :streaming_logs,
12 | :attach,
13 | :stop,
14 | :logs
15 | ]
16 | end
17 |
18 | # --
19 |
20 | def json
21 | {
22 | "State" => {
23 | "ExitCode" => 0
24 | }
25 | }
26 | end
27 |
28 | # --
29 |
30 | def start(*_)
31 | self
32 | end
33 |
34 | # --
35 |
36 | def method_missing(method, *args, &block)
37 | if @mocked.include?(method)
38 | nil else super
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/spec/support/mocks/image.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | module Mocks
6 | class Image
7 | def tag(*args, &block); end
8 | def delete(*args, &block); end
9 | def push(*args, &block); end
10 | def id(*args, &block); end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/spec/support/mocks/repo.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "yaml"
6 |
7 | module Mocks
8 | class Repo
9 | extend Forwardable::Extended
10 |
11 | FS_LAYOUTS = {
12 | :project => [
13 | [:mkdir, "docker"],
14 | [:write, "docker/copy/all/usr/local/bin/hello", "world"],
15 | [:write, "docker/template.yml", "--- { force: false, push: false }\n"],
16 | [:touch, "Dockerfile"]
17 | ],
18 |
19 | :normal => [
20 | [:mkdir, "../../copy"],
21 | [:mkdir, "copy"],
22 | [:mkdir, "copy/all"],
23 | [:mkdir, "copy/tag/latest"],
24 | [:mkdir, "copy/group/normal"],
25 | [:write, "copy/all/usr/local/bin/hello", "world"],
26 | [:touch, "Dockerfile"],
27 | [:with_opts, {
28 | :push => false,
29 | :force => false,
30 | }]
31 | ],
32 |
33 | :scratch => [
34 | [:mkdir, "../../copy"],
35 | [:mkdir, "copy"],
36 | [:mkdir, "copy/all"],
37 | [:mkdir, "copy/tag/latest"],
38 | [:mkdir, "copy/group/normal"],
39 | [:write, "copy/all/usr/local/bin/hello", "world"],
40 | [:write, "Rootfs", "hello"],
41 | [:mkdir, "copy/rootfs/"],
42 | [:with_opts, {
43 | :push => false,
44 | :force => false,
45 | }]
46 | ]
47 | }
48 |
49 | # --
50 |
51 | FS_LAYOUTS[:rootfs] = FS_LAYOUTS[
52 | :scratch
53 | ]
54 |
55 | # --
56 |
57 | FS_LAYOUTS.freeze
58 | attr_reader :hashes
59 | attr_reader :original_pwd
60 | attr_reader :context
61 | attr_reader :root
62 |
63 | # --
64 |
65 | def initialize(context)
66 | @original_pwd = Dir.pwd
67 | @root = Pathutil.new(Dir.mktmpdir).realpath
68 | @context = context
69 |
70 | @hashes = HashWithIndifferentAccess.new({
71 | :cli => {},
72 | :init => {
73 | :name => "default"
74 | }
75 | })
76 |
77 | rescue
78 | if @root && @root.exist?
79 | then @root.rm_rf
80 | end
81 |
82 | raise
83 | end
84 |
85 | # --
86 | # Adds a tag to opts.yml properly, on your behalf.
87 | # --
88 |
89 | def add_tag(name, group: :normal)
90 | return with_opts(:tags => {
91 | name => group
92 | })
93 | end
94 |
95 | # --
96 | # Adds an alias on your behalf, properly.
97 | # --
98 |
99 | def add_alias(name, tag: :default)
100 | return with_opts(:aliases => {
101 | name => tag
102 | })
103 | end
104 |
105 | # --
106 | # Determines if a type and layout are valid to use, AKA within the list.
107 | # Example: mocked_repo.valid_layout?(:scratch, :simple) #=> true
108 | # --
109 |
110 | def valid_layout?(type)
111 | return FS_LAYOUTS.key?(
112 | type
113 | )
114 | end
115 |
116 | # --
117 | # Initialize and write all of the files for a given layout, making it
118 | # possible to run `to_repo` and get back a valid repository to test.
119 | # type - the type of layout you wish to use.
120 | # lyout - the layout you are using.
121 | #
122 | # Example:
123 | # mocked_repo.init({
124 | # :type => :normal,
125 | # :layout => :complex
126 | # )
127 | #
128 | # File: /tmp/*/repos/*/copy
129 | # File: /tmp/*/repos/*/Dockerfile
130 | # File: /tmp/*/repos/*/copy/usr/local/bin/hello
131 | # File: /tmp/*/repos/*/opts.yml
132 | # --
133 |
134 | def init(type: :normal)
135 | if !valid_layout?(type)
136 | raise ArgumentError, "Unknown layout type (#{
137 | type
138 | })"
139 |
140 | else
141 | FS_LAYOUTS[@fs_layout = type].each do |(m, *a)|
142 | send m, *a
143 | end
144 |
145 | self
146 | end
147 | end
148 |
149 | # --
150 | # Options being passed to `Repo`.
151 | # --
152 |
153 | def with_cli_opts(args)
154 | @hashes[:cli] = @hashes[:cli].deep_merge(
155 | args.stringify
156 | )
157 |
158 | self
159 | end
160 |
161 | # --
162 |
163 | def with_repo_init(hash)
164 | @hashes[:init] = @hashes[:init].deep_merge(
165 | hash.stringify
166 | )
167 |
168 | self
169 | end
170 |
171 | # --
172 |
173 | def clear_opts
174 | @hashes[:opts] = {}
175 | repo_dir.join(Docker::Template::Meta.opts_file).rm_f
176 | self
177 | end
178 |
179 | # --
180 | # Example: mocked_repo.with_opts(:hello => :world)
181 | # Write the hash data into the repos `opts.yml`
182 | # --
183 |
184 | def with_opts(opts)
185 | @hashes[:opts] ||= repo_dir.join(Docker::Template::Meta.opts_file).read_yaml
186 | @hashes[:opts] = @hashes[:opts].deep_merge(opts.stringify)
187 | repo_dir.join("opts.yml").write(
188 | @hashes[:opts].to_yaml
189 | )
190 |
191 | self
192 | end
193 |
194 | # --
195 |
196 | def to_img
197 | Docker::Image.get(
198 | to_repo.to_s
199 | )
200 |
201 | rescue => e
202 | if e.is_a?(Docker::Error::NotFoundError)
203 | then nil else raise e
204 | end
205 | end
206 |
207 | # --
208 |
209 | def to_repo
210 | repo_dir
211 | Docker::Template::Repo.new(
212 | @hashes[:init].dup, @hashes[:cli].dup
213 | )
214 | end
215 |
216 | # --
217 | # to_rootfs, to_normal, to_scratch
218 | # --
219 |
220 | %W(Scratch Normal Rootfs).each do |k|
221 | define_method "to_#{k.downcase}" do
222 | Docker::Template::Builder.const_get(k).new(
223 | to_repo
224 | )
225 | end
226 | end
227 |
228 | # --
229 |
230 | def empty
231 | @emptied = true
232 | @root.glob("*").map(
233 | &:rm_rf
234 | )
235 |
236 | self
237 | end
238 |
239 | # --
240 | # Example: mocked_repo.mkdir("hello")
241 | # Make an empty directory!
242 | # --
243 |
244 | def mkdir(dir)
245 | repo_dir.join(dir).mkdir_p
246 | self
247 | end
248 |
249 | # --
250 | # Example: mocked_rep.write("hello", "world")
251 | # Make a file and write data to it.
252 | # --
253 |
254 | def write(file, data)
255 | path = repo_dir.join(file)
256 | path.dirname.mkdir_p
257 | path.write(
258 | data
259 | )
260 |
261 | self
262 | end
263 |
264 | # --
265 | # Example: mocked_repo.touch("my_file")
266 | # Create an empty file of your choosing.
267 | # --
268 |
269 | def touch(file)
270 | repo_dir.join(file).touch
271 | self
272 | end
273 |
274 | # --
275 | # Symlink a file within the current repository directory.
276 | # Example: mocked_repo.symlink("/etc", "etc")
277 | # --
278 |
279 | def symlink(target, name)
280 | repo_dir.join(target).symlink(repo_dir.join(
281 | name
282 | ))
283 |
284 | self
285 | end
286 |
287 | # --
288 | # Delete a file from the current repository directory.
289 | # Example: mocked_repo.delete("hello")
290 | # --
291 |
292 | def delete(file)
293 | repo_dir.join(file).rm_r
294 | self
295 | end
296 |
297 | # --
298 | # Link to a file outside of any known and recognized root.
299 | # Example: mocked_repo.symlink("/etc", "etc")
300 | # --
301 |
302 | def external_symlink(target, name)
303 | Pathutil.new(target).symlink(repo_dir.join(
304 | name
305 | ))
306 |
307 | self
308 | end
309 |
310 | # --
311 |
312 | def setup
313 | @context.allow(Dir).to @context.receive(:pwd).and_return @root if rspec?
314 | @context.allow(Docker::Template).to context.receive(:root).and_return @root if rspec?
315 | Docker::Template.stub(:root).and_return @root unless rspec?
316 | Dir.stub(:pwd).and_return @repo.to_s unless rspec?
317 | end
318 |
319 | # --
320 |
321 | def teardown
322 | @root.rm_rf
323 | end
324 |
325 | # --
326 |
327 | def rspec?
328 | @context.is_a?(
329 | RSpec::Core::ExampleGroup
330 | )
331 | end
332 |
333 | # --
334 |
335 | private
336 | def repo_dir
337 | rtn = @root if @fs_layout == :project
338 | rtn = @root.join(Docker::Template::Meta::DEFAULTS[:repos_dir], \
339 | @hashes[:init][:name]) unless @fs_layout == :project
340 | rtn.mkdir_p unless rtn.exist? || emptied?
341 | rtn
342 | end
343 |
344 | # --
345 |
346 | alias clear empty
347 | alias to_project to_normal
348 | rb_delegate :emptied?, :to => :@emptied, :type => :ivar, :bool => true
349 | rb_delegate :join, :to => :@root
350 | end
351 | end
352 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/auth_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Auth do
7 | include_context :repos
8 |
9 | before :all do
10 | class AuthPathutilWrapper
11 | def expand_path
12 | self
13 | end
14 |
15 | #
16 |
17 | def read_json
18 | return {
19 | "auths" => {
20 | "server.com" => {
21 | "email" => "user@example.com",
22 | "auth" => Base64.encode64(
23 | "username:password"
24 | )
25 | }
26 | }
27 | }
28 | end
29 | end
30 | end
31 |
32 | #
33 |
34 | before do
35 | allow(Pathutil).to receive(:new).and_call_original
36 | allow(Docker).to receive(:authenticate!).and_return(nil)
37 | allow_any_instance_of(described_class).to receive(:auth_with_env?).and_return false
38 | allow(Pathutil).to receive(:new).with("~/.docker/config.json") \
39 | .and_return(AuthPathutilWrapper.new)
40 | end
41 |
42 | #
43 |
44 | describe "#auth", :skip_auth => true do
45 | context "when it cannot authenticate" do
46 | before do
47 | allow(Docker).to receive :authenticate! do
48 | raise Docker::Error::AuthenticationError
49 | end
50 | end
51 |
52 | it "should throw" do
53 | expect { described_class.new(mocked_repo.to_repo).auth }.to raise_error(
54 | Docker::Template::Error::UnsuccessfulAuth
55 | )
56 | end
57 | end
58 | end
59 |
60 | #
61 |
62 | describe "auth_from_env" do
63 | before do
64 | allow_any_instance_of(described_class).to receive(:auth_with_env?).and_return true
65 | allow(ENV).to receive(:[]).with("DOCKER_SERVER").and_return("eserver.com")
66 | allow(ENV).to receive(:[]).with("DOCKER_EMAIL").and_return("euser@example.com")
67 | allow(ENV).to receive(:[]).with("DOCKER_PASSWORD").and_return("epassword")
68 | allow(ENV).to receive(:[]).with("DOCKER_USERNAME").and_return("eusername")
69 | end
70 |
71 | #
72 |
73 | it "should authenticate with those credentials" do
74 | expect(Docker).to receive(:authenticate!).with({
75 | "username" => "eusername",
76 | "serveraddress" => "eserver.com",
77 | "email" => "euser@example.com",
78 | "password" => "epassword"
79 | })
80 | end
81 |
82 | #
83 |
84 | context "when the user doesn't set a server" do
85 | before do
86 | allow(ENV).to receive(:[]).with("bamboo_dockerServer").and_return nil
87 | allow(ENV).to receive(:[]).with("DOCKER_SERVER").and_return(
88 | nil
89 | )
90 | end
91 |
92 | #
93 |
94 | it "should use the default" do
95 | expect(Docker).to receive(:authenticate!).with({
96 | "username" => "eusername",
97 | "serveraddress" => described_class::DEFAULT_SERVER,
98 | "email" => "euser@example.com",
99 | "password" => "epassword"
100 | })
101 | end
102 | end
103 | end
104 |
105 | #
106 |
107 | context "_hub_config" do
108 | context "and the user has ~/.docker/config.json" do
109 | it "should read the file" do
110 | expect(Pathutil).to receive(:new).with("~/.docker/config.json").and_return(
111 | AuthPathutilWrapper.new
112 | )
113 | end
114 |
115 | #
116 |
117 | it "should auth with the credentials" do
118 | expect(Docker).to receive(:authenticate!).at_least(:once).with({
119 | "username" => "username",
120 | "serveraddress" => "server.com",
121 | "email" => "user@example.com",
122 | "password" => "password"
123 | })
124 | end
125 | end
126 | end
127 |
128 | #
129 |
130 | after do |ex|
131 | unless ex.metadata[:skip_auth]
132 | described_class.new(mocked_repo.to_repo).auth
133 | end
134 | end
135 | end
136 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/builder/normal_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Builder::Normal, :type => :normal do
7 | include_contexts :docker, :repos
8 |
9 | #
10 |
11 | subject do
12 | mocked_repo.with_repo_init("tag" => "latest")
13 | mocked_repo.to_normal
14 | end
15 |
16 | #
17 |
18 | it "should allow projects" do
19 | expect(described_class.projects_allowed?).to eq(
20 | true
21 | )
22 | end
23 |
24 | #
25 |
26 | it "should allow what Dockerfiles it allows" do
27 | expect(described_class.files).not_to(
28 | be_empty
29 | )
30 | end
31 |
32 | #
33 |
34 | describe "#copy_dockerfile" do
35 | context "when the it's a project build", :type => :project do
36 | it "should pull the Dockerfile from the root" do
37 | expect(Docker::Template).to receive(:root) \
38 | .and_call_original
39 | end
40 | end
41 |
42 | #
43 |
44 | it "should pull the Dockerfile from the repo root" do
45 | expect(subject.repo).to receive(:root) \
46 | .and_call_original
47 | end
48 |
49 | #
50 |
51 | it "should create an ERB context" do
52 | expect(ERB::Context).to receive(:new) \
53 | .and_call_original
54 | end
55 |
56 | #
57 |
58 | after do
59 | subject.send(:setup_context)
60 | subject.teardown
61 | end
62 | end
63 |
64 | #
65 |
66 | describe "#cache_context" do
67 | context "when cache = false" do
68 | before do
69 | subject.repo.meta.merge!({
70 | "cache" => false
71 | })
72 | end
73 |
74 | #
75 |
76 | context do
77 | before do
78 | silence_io do
79 | subject.send(
80 | :cache_context
81 | )
82 | end
83 | end
84 |
85 | #
86 |
87 | it "should not copy all the files" do
88 | expect(subject.repo.cache_dir).not_to(
89 | exist
90 | )
91 | end
92 | end
93 |
94 | #
95 |
96 | context do
97 | it "should not call cache to copy it" do
98 | expect(Docker::Template::Cache).not_to receive(
99 | :context
100 | )
101 | end
102 |
103 | #
104 |
105 | after do
106 | subject.send(
107 | :cache_context
108 | )
109 | end
110 | end
111 | end
112 |
113 | #
114 |
115 | context "when cache = true" do
116 | before do
117 | subject.repo.meta.merge!({
118 | "cache" => true
119 | })
120 | end
121 |
122 | #
123 |
124 | context do
125 | before do
126 | silence_io do
127 | subject.send :setup_context
128 | subject.send :cache_context
129 | end
130 | end
131 |
132 | #
133 |
134 | it "should copy all the files" do
135 | expect(subject.repo.cache_dir).to(
136 | exist
137 | )
138 | end
139 | end
140 |
141 | #
142 |
143 | context do
144 | it "should call the cache to copy it" do
145 | expect(Docker::Template::Cache).to receive(:context).and_return(
146 | nil
147 | )
148 | end
149 |
150 | #
151 |
152 | after do
153 | subject.send(
154 | :cache_context
155 | )
156 | end
157 | end
158 |
159 | #
160 |
161 | after do
162 | subject.teardown
163 | subject.repo.meta.merge!({
164 | "cache" => false
165 | })
166 | end
167 | end
168 | end
169 |
170 | #
171 |
172 | describe "#teardown" do
173 | before do
174 | subject.send :setup_context
175 | subject.teardown
176 | end
177 |
178 | #
179 |
180 | it "should delete the context folder" do
181 | expect(subject.instance_variable_get(:@context)).not_to(
182 | exist
183 | )
184 | end
185 |
186 | #
187 |
188 | context "(img: true)" do
189 | before do
190 | subject.instance_variable_set(
191 | :@img, image_mock
192 | )
193 | end
194 |
195 | #
196 |
197 | it "should try to delete the image" do
198 | expect(image_mock).to receive(
199 | :delete
200 | )
201 | end
202 |
203 | #
204 |
205 | after do
206 | subject.teardown(img: true)
207 | subject.remove_instance_variable(:@img)
208 | end
209 | end
210 | end
211 |
212 | #
213 |
214 | describe "#setup_context" do
215 | before do
216 | subject.send(
217 | :setup_context
218 | )
219 | end
220 |
221 | #
222 |
223 | it "should copy the Dockerfile" do
224 | expect(subject.instance_variable_get(:@context).find.map(&:to_s)).to include match(
225 | /Dockerfile\Z/
226 | )
227 | end
228 |
229 | #
230 |
231 | after do
232 | subject.teardown
233 | end
234 | end
235 | end
236 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/builder/rootfs_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | RSpec.describe Docker::Template::Builder::Rootfs do
7 | include_contexts :docker, :repos
8 |
9 | #
10 |
11 | subject do
12 | mocked_repo.to_rootfs
13 | end
14 |
15 | #
16 |
17 | before do
18 | mocked_repo.with_repo_init({
19 | "tag" => "default"
20 | })
21 | end
22 |
23 | #
24 |
25 | it "should flag itself as a sub-builder" do
26 | expect(described_class.sub?).to eq(
27 | true
28 | )
29 | end
30 |
31 | #
32 |
33 | it "should not allow projects" do
34 | expect(described_class.projects_allowed?).to(
35 | be_falsey
36 | )
37 | end
38 |
39 | #
40 |
41 | it "should support the types of Dockerfiles it supports" do
42 | expect(described_class.files).not_to(
43 | be_empty
44 | )
45 | end
46 |
47 | #
48 |
49 | describe "#data" do
50 | it "should add the FROM line" do
51 | expect(subject.data).to match(
52 | %r!\AFROM [a-z]+/alpine!
53 | )
54 | end
55 | end
56 |
57 | #
58 |
59 | describe "#copy_rootfs" do
60 | before do
61 | subject.send(
62 | :setup_context
63 | )
64 | end
65 |
66 | #
67 |
68 | it "should copy", :type => :rootfs, :layout => :complex do
69 | expect_any_instance_of(Pathutil).to receive(:safe_copy).and_return(
70 | nil
71 | )
72 | end
73 |
74 | #
75 |
76 | after do
77 | subject.send :copy_rootfs
78 | subject.teardown
79 | end
80 | end
81 |
82 | #
83 |
84 | describe "#teardown" do
85 | before do
86 | silence_io do
87 | subject.build
88 | end
89 | end
90 |
91 | #
92 |
93 | it "should delete the context it created" do
94 | expect(subject.instance_variable_get(:@context)).not_to(
95 | exist
96 | )
97 | end
98 |
99 | #
100 |
101 | context "(img: true)" do
102 | context do
103 | it "should delete the image" do
104 | expect(image_mock).to receive(:delete).and_return(
105 | nil
106 | )
107 | end
108 |
109 | #
110 |
111 | after do
112 | subject.teardown({
113 | :img => true
114 | })
115 | end
116 | end
117 | end
118 | end
119 | end
120 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/builder/scratch_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Builder::Scratch do
7 | include_contexts :docker, :repos
8 |
9 | #
10 |
11 | before do
12 | mocked_repo.init({
13 | :type => :normal
14 | })
15 | end
16 |
17 | #
18 |
19 | subject do
20 | mocked_repo.with_repo_init("tag" => "latest")
21 | mocked_repo.to_scratch
22 | end
23 |
24 | #
25 |
26 | before do
27 | allow(subject).to receive(:create_args).and_return({})
28 | allow(subject).to receive(:verify_context).and_return nil
29 | allow(subject).to receive(:start_args).and_return({})
30 | end
31 |
32 | #
33 |
34 | it "should not allow projects" do
35 | expect(described_class.projects_allowed?).to(
36 | be_falsey
37 | )
38 | end
39 |
40 | #
41 |
42 | it "should support the types of Dockerfiles it supports" do
43 | expect(described_class.files).not_to(
44 | be_empty
45 | )
46 | end
47 |
48 | #
49 |
50 | describe "#data" do
51 | before do
52 | subject.send(
53 | :setup_context
54 | )
55 | end
56 |
57 | #
58 |
59 | it "adds the TARGZ file" do
60 | expect(subject.data).to match(
61 | %r!^ADD .*\.tar\.gz /$!m
62 | )
63 | end
64 |
65 | #
66 |
67 | it "adds the MAINTAINER" do
68 | expect(subject.data).to match %r!MAINTAINER #{Regexp.escape(
69 | subject.repo.meta["maintainer"]
70 | )}!
71 | end
72 |
73 | #
74 |
75 | it "should not return empty data" do
76 | expect(subject.data).not_to(
77 | be_empty
78 | )
79 | end
80 |
81 | #
82 |
83 | after do
84 | subject.teardown
85 | end
86 | end
87 |
88 | #
89 |
90 | describe "#setup_context" do
91 | before do
92 | subject.send(
93 | :setup_context
94 | )
95 | end
96 |
97 | #
98 |
99 | it "should copy the Dockerfile" do
100 | expect(subject.instance_variable_get(:@context).find.map(&:to_s)).to include match(
101 | /\/Dockerfile\Z/
102 | )
103 | end
104 |
105 | #
106 |
107 | after do
108 | subject.teardown
109 | end
110 | end
111 |
112 | #
113 |
114 | describe "#teardown" do
115 | before do
116 | silence_io do
117 | subject.build
118 | end
119 | end
120 |
121 | #
122 |
123 | context "(img: true)" do
124 | it "should delete the image" do
125 | expect(image_mock).to receive(
126 | :delete
127 | )
128 | end
129 |
130 | #
131 |
132 | after do
133 | subject.teardown({
134 | :img => true
135 | })
136 | end
137 | end
138 |
139 | #
140 |
141 | context do
142 | let :pathname do
143 | subject.instance_variable_get(
144 | :@context
145 | )
146 | end
147 |
148 | #
149 |
150 | before do
151 | subject.teardown
152 | end
153 |
154 | #
155 |
156 | it "should remove the context" do
157 | expect(pathname).not_to(
158 | exist
159 | )
160 | end
161 | end
162 | end
163 |
164 | #
165 |
166 | describe "#build_context" do
167 | before do
168 | allow_any_instance_of(Docker::Template::Builder::Rootfs).to receive(:build).and_return(
169 | nil
170 | )
171 | end
172 |
173 | after do |ex|
174 | unless ex.metadata[:nobuild]
175 | subject.send(
176 | :build_context
177 | )
178 | end
179 | end
180 |
181 | #
182 |
183 | it "should build the rootfs context" do
184 | expect(subject.rootfs).to receive(
185 | :build
186 | )
187 | end
188 |
189 | #
190 |
191 | it "should stop the rootfs container once it's done" do
192 | expect(container_mock).to receive(
193 | :stop
194 | )
195 | end
196 |
197 | #
198 |
199 | it "should teardown the rootfs image" do
200 | expect(subject.rootfs).to receive(
201 | :teardown
202 | )
203 | end
204 |
205 | #
206 |
207 | it "should delete the rootfs container" do
208 | expect(container_mock).to receive(
209 | :delete
210 | )
211 | end
212 |
213 | #
214 |
215 | context "when an image exists badly" do
216 | before do
217 | allow(container_mock).to receive :json do
218 | {
219 | "State" => {
220 | "ExitCode" => 1
221 | }
222 | }
223 | end
224 | end
225 |
226 | #
227 |
228 | it "should raise an error", :nobuild do
229 | expect { subject.send :build_context }.to raise_error(
230 | Docker::Template::Error::BadExitStatus
231 | )
232 | end
233 | end
234 | end
235 | end
236 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/builder_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Builder do
7 | include_contexts :docker, :repos
8 |
9 | #
10 |
11 | after do |ex|
12 | unless ex.metadata[:skip_teardown]
13 | subject.teardown
14 | end
15 | end
16 |
17 | #
18 |
19 | subject do |ex|
20 | mocked_repo.send(
21 | "to_#{ex.metadata[:type]}"
22 | )
23 | end
24 |
25 | #
26 |
27 | before do
28 | # rubocop:disable Style/SpaceInsideParens
29 | allow(subject).to receive(:verify_context).and_return nil
30 | allow(subject).to receive( :build_context).and_return nil
31 | # rubocop:enable Style/SpaceInsideParens
32 | end
33 |
34 | #
35 |
36 | describe "#.sub?" do
37 | it "is false by default..." do
38 | expect(described_class.sub?).to eq(
39 | false
40 | )
41 | end
42 | end
43 |
44 | #
45 |
46 | describe ".files" do
47 | it "should be a reference method and empty" do
48 | expect(described_class.files).to(
49 | be_empty
50 | )
51 | end
52 | end
53 |
54 | #
55 |
56 | describe ".projects_allowed?" do
57 | context "when the user wishes to allow project builds" do
58 | before :all do
59 | class BuilderTestMock < Docker::Template::Builder
60 | projects_allowed!
61 | end
62 | end
63 |
64 | it "should be true" do
65 | expect(BuilderTestMock.projects_allowed?).to eq(
66 | true
67 | )
68 | end
69 |
70 | after :all do
71 | Object.send(
72 | :remove_const, :BuilderTestMock
73 | )
74 | end
75 | end
76 |
77 | #
78 |
79 | it "should be nil by default" do
80 | expect(described_class.projects_allowed?).to(
81 | be_falsey
82 | )
83 | end
84 | end
85 |
86 | #
87 |
88 | describe ".all" do
89 | it "should hold a list of all classes" do
90 | expect(described_class.all).not_to(
91 | be_empty
92 | )
93 | end
94 | end
95 |
96 | #
97 |
98 | describe "#alias?" do
99 | it "should return false if the repository is not an alias" do
100 | expect(mocked_repo.to_normal.alias?).to eq(
101 | false
102 | )
103 | end
104 |
105 | #
106 |
107 | context "when a simple alias" do
108 | before do
109 | mocked_repo.add_alias(
110 | "hello"
111 | )
112 | end
113 |
114 | #
115 |
116 | it "should return true" do
117 | expect(mocked_repo.with_repo_init("tag" => "hello").to_normal.alias?).to eq(
118 | true
119 | )
120 | end
121 | end
122 |
123 | #
124 |
125 | context "when a complex alias" do
126 | before do
127 | mocked_repo.with_repo_init "tag" => "hello"
128 | mocked_repo.add_alias "hello"
129 | mocked_repo.with_opts({
130 | "env" => {
131 | "tag" => {
132 | "hello" => [
133 | "world"
134 | ]
135 | }
136 | }
137 | })
138 | end
139 |
140 | #
141 |
142 | it "should return false" do
143 | expect(mocked_repo.to_normal.alias?).to eq(
144 | false
145 | )
146 | end
147 | end
148 | end
149 |
150 | #
151 |
152 | describe "#rootfs?" do
153 | it "should return true if it's a rootfs" do
154 | expect(mocked_repo.to_rootfs.rootfs?).to eq(
155 | true
156 | )
157 | end
158 |
159 | #
160 |
161 | context "when it's not a rootfs" do
162 | it "should return false" do
163 | expect(mocked_repo.to_normal.rootfs?).to eq(
164 | false
165 | )
166 | end
167 | end
168 | end
169 |
170 | #
171 |
172 | describe "#normal?" do
173 | it "should return true if it's a normal" do
174 | expect(mocked_repo.to_normal.normal?).to eq(
175 | true
176 | )
177 | end
178 |
179 | #
180 |
181 | context "when it's not normal" do
182 | it "should return false" do
183 | expect(mocked_repo.to_rootfs.normal?).to eq(
184 | false
185 | )
186 | end
187 | end
188 | end
189 |
190 | #
191 |
192 | describe "#scratch?" do
193 | context do
194 | before do
195 | mocked_repo.with_opts({
196 | :type => :scratch
197 | })
198 | end
199 |
200 | #
201 |
202 | it "should return true if it's a scratch" do
203 | expect(mocked_repo.to_scratch.scratch?).to eq(
204 | true
205 | )
206 | end
207 | end
208 |
209 | context "when it's not a scratch" do
210 | it "should return false" do
211 | expect(mocked_repo.to_normal.scratch?).to eq(
212 | false
213 | )
214 | end
215 | end
216 | end
217 |
218 | #
219 |
220 | describe "#aliased_img" do
221 | context "when there is no alias" do
222 | it "should return nothing" do
223 | expect(mocked_repo.to_normal.aliased_img).to eq(
224 | nil
225 | )
226 | end
227 | end
228 |
229 | #
230 |
231 | context "when the image is an alias of another image" do
232 | before do
233 | mocked_repo.add_alias "world"
234 | mocked_repo.with_repo_init({
235 | "tag" => "world"
236 | })
237 | end
238 |
239 | it "should try and pull the image" do
240 | expect(Docker::Image).to receive(:get).and_return(
241 | nil
242 | )
243 | end
244 |
245 | #
246 |
247 | after do
248 | mocked_repo.to_normal \
249 | .aliased_img
250 | end
251 | end
252 | end
253 |
254 | #
255 |
256 | describe "#push" do
257 | before do
258 | subject.repo.meta.merge!({
259 | "push" => true
260 | })
261 | end
262 |
263 | #
264 |
265 | after do
266 | silence_io { subject.build }
267 | subject.repo.meta.merge!({
268 | "push" => false
269 | })
270 | end
271 |
272 | #
273 |
274 | it "should try to auth" do
275 | expect_any_instance_of(Docker::Template::Auth).to \
276 | receive(:auth).and_return(
277 | nil
278 | )
279 | end
280 |
281 | #
282 |
283 | context do
284 | before do
285 | allow_any_instance_of(Docker::Template::Auth).to \
286 | receive(:auth).and_return(
287 | nil
288 | )
289 | end
290 |
291 | #
292 |
293 | it "should try to push" do
294 | expect(image_mock).to receive(:push).and_return(
295 | nil
296 | )
297 | end
298 | end
299 |
300 | #
301 |
302 | context "when push == false" do
303 | before do
304 | subject.repo.meta.merge!({
305 | "push" => false
306 | })
307 | end
308 |
309 | #
310 |
311 | it "should not try to push the repo" do
312 | expect(image_mock).not_to receive(
313 | :push
314 | )
315 | end
316 | end
317 | end
318 |
319 | #
320 |
321 | context do
322 | before do
323 | subject.send(
324 | :setup_context
325 | )
326 | end
327 |
328 | #
329 |
330 | describe "#copy_global" do
331 | context "when it's a scratch image" do
332 | it "should copy", :type => :scratch do
333 | expect_any_instance_of(Pathutil).to receive(:safe_copy).and_return(
334 | nil
335 | )
336 | end
337 | end
338 |
339 | #
340 |
341 | context "when it's a normal image" do
342 | it "should copy", :type => :normal do
343 | expect_any_instance_of(Pathutil).to receive(:safe_copy).and_return(
344 | nil
345 | )
346 | end
347 | end
348 |
349 | #
350 |
351 | context "when it's a rootfs image" do
352 | it "should not copy", :type => :rootfs do
353 | expect_any_instance_of(Pathutil).not_to receive(
354 | :safe_copy
355 | )
356 | end
357 | end
358 |
359 | #
360 |
361 | after do
362 | subject.send(
363 | :copy_global
364 | )
365 | end
366 | end
367 |
368 | #
369 |
370 | describe "#copy_project", :type => :project do
371 | it "should copy" do
372 | expect(Docker::Template.root).to receive(:safe_copy).and_return(
373 | nil
374 | )
375 | end
376 |
377 | #
378 |
379 | it "should pass ignores" do
380 | hash = { :root => anything, :ignore => anything }
381 | expect(Docker::Template.root).to receive(:safe_copy).with(anything, hash).and_return(
382 | nil
383 | )
384 | end
385 |
386 | #
387 |
388 | after do
389 | subject.send(
390 | :copy_project
391 | )
392 | end
393 | end
394 |
395 | #
396 |
397 | shared_examples :copy do
398 | context "when it's scratch" do
399 | it "should copy", :type => :scratch do
400 | expect_any_instance_of(Pathutil).to receive(:safe_copy).and_return(
401 | nil
402 | )
403 | end
404 | end
405 |
406 | #
407 |
408 | context "when it's normal" do
409 | it "should copy", :type => :normal do
410 | expect_any_instance_of(Pathutil).to receive(:safe_copy).and_return(
411 | nil
412 | )
413 | end
414 | end
415 |
416 | #
417 |
418 | context "when it's a rootfs" do
419 | it "should not copy", :type => :rootfs do
420 | expect_any_instance_of(Pathutil).not_to receive(
421 | :safe_copy
422 | )
423 | end
424 |
425 | #
426 |
427 | context "when simple_copy?", :simple_copy do
428 | it "should not copy", :type => :rootfs, :layout => :simple do
429 | expect_any_instance_of(Pathutil).not_to receive(
430 | :safe_copy
431 | )
432 | end
433 | end
434 | end
435 | end
436 |
437 | #
438 |
439 | describe "#copy_tag" do
440 | it_behaves_like(
441 | :copy
442 | )
443 |
444 | #
445 |
446 | after do
447 | subject.send(
448 | :copy_tag
449 | )
450 | end
451 | end
452 |
453 | describe "#copy_group" do
454 | before do
455 | subject.repo.meta["tags"] = {
456 | "latest" => "normal"
457 | }
458 | end
459 |
460 | after do
461 | subject.send(
462 | :copy_group
463 | )
464 | end
465 |
466 | #
467 |
468 | it_behaves_like(
469 | :copy
470 | )
471 | end
472 |
473 | describe "#copy_all" do
474 | after do
475 | subject.send(
476 | :copy_all
477 | )
478 | end
479 |
480 | it_behaves_like(
481 | :copy
482 | )
483 | end
484 | end
485 |
486 | #
487 |
488 | describe "#build" do
489 | after do |ex|
490 | unless ex.metadata[:skip]
491 | ex.metadata[:noisey] ? subject.build : silence_io do
492 | subject.build
493 | end
494 | end
495 | end
496 |
497 | #
498 |
499 | shared_examples :build do
500 | context do
501 | before do
502 | allow(subject.repo).to receive(:buildable?).and_return false
503 | allow(subject).to receive(:build) \
504 | .and_call_original
505 | end
506 |
507 | #
508 |
509 | it "should check if the repo is buildable" do
510 | expect(subject.repo).to receive(:buildable?).and_return(
511 | nil
512 | )
513 | end
514 | end
515 |
516 | #
517 |
518 | context "when building? is set to false" do
519 | before do
520 | allow(subject.repo).to receive(:buildable?).and_return false
521 | allow(subject).to receive(:build) \
522 | .and_call_original
523 | end
524 |
525 | #
526 |
527 | it "should not build" do
528 | expect(subject).not_to receive(
529 | :chdir_build
530 | )
531 | end
532 | end
533 |
534 | #
535 |
536 | it "should build from the context" do
537 | expect(Docker::Image).to receive(
538 | :build_from_dir
539 | )
540 | end
541 |
542 | #
543 |
544 | it "should teardown", :skip_teardown => true do
545 | expect(subject).to receive(:teardown) \
546 | .and_call_original
547 | end
548 |
549 | #
550 |
551 | it "should not throw any errors" do
552 | expect { silence_io { subject.build }}.not_to(
553 | raise_error
554 | )
555 | end
556 |
557 | #
558 |
559 | it "should notify of the build", :noisey do
560 | expect(Docker::Template::Notify).to receive(:build).and_return(
561 | nil
562 | )
563 | end
564 |
565 | #
566 |
567 | it "should clear the screen" do
568 | expect(Simple::Ansi).to receive(:clear).and_return(
569 | nil
570 | )
571 | end
572 | end
573 |
574 | #
575 |
576 | context "when @subject.type == normal", :type => :normal do
577 | it_behaves_like(
578 | :build
579 | )
580 | end
581 |
582 | #
583 |
584 | context "when subject.type == scratch" do
585 | before do
586 | # rubocop:disable Style/SpaceInsideParens
587 | allow(subject).to receive(:create_args).and_return({})
588 | allow(subject).to receive( :start_args).and_return({})
589 | # rubocop:enable Style/SpaceInsideParens
590 | end
591 |
592 | it_behaves_like(
593 | :build
594 | )
595 | end
596 | end
597 | end
598 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/cache_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Cache do
7 | include_context :repos
8 |
9 | #
10 |
11 | describe "#context" do
12 | context "when told to cache a non-aliased image" do
13 | before do
14 | subject.send :setup
15 | silence_io do
16 | described_class.context(
17 | subject, subject.context
18 | )
19 | end
20 | end
21 |
22 | #
23 |
24 | subject do
25 | mocked_repo.to_normal
26 | end
27 |
28 | #
29 |
30 | it "should cache the context", :type => :normal do
31 | expect(subject.repo.cache_dir).to(
32 | exist
33 | )
34 | end
35 |
36 | #
37 |
38 | after do
39 | subject.teardown
40 | end
41 | end
42 |
43 | #
44 |
45 | context "when told to cache an aliased image" do
46 | subject do
47 | mocked_repo.to_normal
48 | end
49 |
50 | #
51 |
52 | before do
53 | mocked_repo.add_alias :hello
54 | mocked_repo.with_repo_init :tag => :hello
55 | mocked_repo.with_opts :cache => true
56 |
57 | silence_io do
58 | subject.aliased_repo.builder.send :setup
59 | described_class.aliased_context(
60 | subject
61 | )
62 | end
63 | end
64 |
65 | #
66 |
67 | it "should copy it's parent's context", :type => :normal do
68 | expect(subject.repo.cache_dir).to(
69 | exist
70 | )
71 | end
72 |
73 | #
74 |
75 | after do
76 | subject.aliased_repo.builder.teardown
77 | end
78 | end
79 | end
80 | end
81 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/cli/build_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | require "docker/template/cli"
7 | describe Docker::Template::CLI::Build do
8 | include_context :repos
9 |
10 | before do
11 | allow(Docker::Template::Repo).to receive(:build).and_return(
12 | nil
13 | )
14 | end
15 |
16 | #
17 |
18 | let :parser do
19 | Docker::Template::Parser
20 | end
21 |
22 | #
23 |
24 | subject do
25 | described_class.new(
26 | [], {}
27 | )
28 | end
29 |
30 | #
31 |
32 | describe "#initialize", :start => false do
33 | it "pulls the repositories" do
34 | expect(parser).to receive(:new).with([], {}) \
35 | .and_call_original
36 | end
37 |
38 | #
39 |
40 | it "parses the repositories" do
41 | expect_any_instance_of(parser).to receive(:parse) \
42 | .and_call_original
43 | end
44 |
45 | #
46 |
47 | after do
48 | described_class.new(
49 | [], {}
50 | )
51 | end
52 | end
53 |
54 | #
55 |
56 | describe "#start" do
57 | it "profiles by default" do
58 | expect(subject).to receive(:_profile) \
59 | .and_call_original
60 | end
61 | end
62 |
63 | #
64 |
65 | context "--diff or diff: true", :ruby => "!jruby" do
66 | it "should reselect the repositories" do
67 | expect(subject).to receive(:changed!).and_return(
68 | []
69 | )
70 | end
71 |
72 | #
73 |
74 | subject do
75 | described_class.new([], {
76 | :diff => true
77 | })
78 | end
79 | end
80 |
81 | #
82 |
83 | context "--exclude or exclude: []", :ruby => "!jruby" do
84 | it "should reselect the repositories" do
85 | expect(subject).to receive(:exclude!).and_return(
86 | []
87 | )
88 | end
89 |
90 | #
91 |
92 | subject do
93 | described_class.new([], {
94 | :exclude => [
95 | :hello
96 | ]
97 | })
98 | end
99 | end
100 |
101 | #
102 |
103 | context "--profile or profile: true", :ruby => "!jruby" do
104 | before :all do
105 | require "memory_profiler"
106 | end
107 |
108 | #
109 |
110 | subject do
111 | described_class.new([], {
112 | :profile => true
113 | })
114 | end
115 |
116 | #
117 |
118 | before :all do
119 | class ProfilerMock
120 | def pretty_print(*)
121 | return
122 | end
123 | end
124 | end
125 |
126 | #
127 |
128 | before do
129 | allow(MemoryProfiler).to receive(:report) \
130 | .and_return(
131 | ProfilerMock.new
132 | )
133 | end
134 |
135 | #
136 |
137 | it "should profile" do
138 | expect(MemoryProfiler).to receive(:report) \
139 | .and_return(
140 | ProfilerMock.new
141 | )
142 | end
143 |
144 | #
145 |
146 | it "should report" do
147 | expect_any_instance_of(ProfilerMock).to receive(
148 | :pretty_print
149 | )
150 | end
151 | end
152 |
153 | #
154 |
155 | describe "#git_reselect_repos", :start => false, :ruby => "!jruby" do
156 | before do
157 | # Require uses #_require so we need to shim that out.
158 | allow(Docker::Template).to receive(:require).with("rugged").and_return(
159 | true
160 | )
161 | end
162 |
163 | #
164 |
165 | let :git do
166 | require "rugged"
167 | Rugged::Repository.init_at(
168 | mocked_repo.root.to_s
169 | )
170 | end
171 |
172 | #
173 |
174 | before do
175 | git.config.store "user.email", "user@example.com"
176 | git.config.store "user.name", "Some User"
177 | git.index.add_all
178 |
179 | Rugged::Commit.create git, {
180 | :message => "Init.",
181 | :tree => git.index.write_tree(git),
182 | :update_ref => "HEAD",
183 | :parents => []
184 | }
185 |
186 | mocked_repo.add_tag "latest"
187 | mocked_repo.join("repos/hello").mkdir_p
188 | mocked_repo.join("repos/hello/opts.yml").write({
189 | "tags" => {
190 | "latest" => "normal"
191 | }
192 | }.to_yaml)
193 | end
194 |
195 | #
196 |
197 | it "should pull repositories from the last commit" do
198 | expect_any_instance_of(Rugged::Repository).to receive(:last_commit) \
199 | .and_call_original
200 | end
201 |
202 | #
203 |
204 | it "should return all modified repositories" do
205 | expect(subject.changed!.count).to eq 1
206 | expect(subject.changed!.first.name).to eq(
207 | "default"
208 | )
209 | end
210 |
211 | #
212 |
213 | context "when argv = [val, val]" do
214 | it "should drop repos from that list that are not modified" do
215 | expect(described_class.new(%w(hello default), {}).changed!.count).to eq(
216 | 1
217 | )
218 | end
219 | end
220 |
221 | #
222 |
223 | after do
224 | subject.changed!
225 | end
226 | end
227 |
228 | #
229 |
230 | after do |ex|
231 | unless ex.metadata[:start] == false
232 | subject.start
233 | end
234 | end
235 | end
236 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/cli/list_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | require "active_support/core_ext/string/strip"
7 | describe Docker::Template::CLI::List do
8 | include_contexts :repos
9 |
10 | #
11 |
12 | subject do
13 | Simple::Ansi.strip(described_class.build).gsub(
14 | /(├─ |│)/, ""
15 | )
16 | end
17 |
18 | #
19 |
20 | before do
21 | mocked_repo.clear_opts.with_opts({
22 | :user => :user,
23 | :tags => {
24 | :tag => :normal
25 | }
26 | })
27 | end
28 |
29 | #
30 |
31 | it "should color everything for readability" do
32 | expect(Simple::Ansi.has?(described_class.build)).to eq(
33 | true
34 | )
35 | end
36 |
37 | #
38 |
39 | it "should output user, repo, and tags" do
40 | expect(subject).to eq <<-STR.strip_heredoc
41 | [user] user
42 | [repo] default
43 | [tag] tag
44 | STR
45 | end
46 |
47 | #
48 |
49 | context "when given a true remote" do
50 | before do
51 | mocked_repo.with_opts({
52 | :aliases => {
53 | :alias => "remote/repo:tag"
54 | }
55 | })
56 | end
57 |
58 | #
59 |
60 | it "should output aliases under that true remote" do
61 | expect(subject).to eq <<-STR.strip_heredoc
62 | [user] user
63 | [repo] default
64 | [tag] tag
65 | [remote] remote/repo:tag
66 | [alias] alias
67 | STR
68 | end
69 | end
70 |
71 | #
72 |
73 | context "when a tag has aliases" do
74 | before do
75 | mocked_repo.with_opts({
76 | :aliases => {
77 | :alias => :tag
78 | }
79 | })
80 | end
81 |
82 | #
83 |
84 | it "should output the aliases inside of the tag" do
85 | expect(subject).to eq <<-STR.strip_heredoc
86 | [user] user
87 | [repo] default
88 | [tag] tag
89 | [alias] alias
90 | STR
91 | end
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/error_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Error do
7 | it { is_expected.to have_const :BadRepoName }
8 | it { is_expected.to have_const :BadExitStatus }
9 | it { is_expected.to have_const :NoSetupContext }
10 | it { is_expected.to have_const :UnsuccessfulAuth }
11 | it { is_expected.to have_const :InvalidTargzFile }
12 | it { is_expected.to have_const :InvalidYAMLFile }
13 | it { is_expected.to have_const :NotImplemented }
14 | it { is_expected.to have_const :RepoNotFound }
15 | it { is_expected.to have_const :ImageNotFound }
16 | end
17 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/logger_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Logger do
7 | subject do
8 | described_class
9 | end
10 |
11 | #
12 |
13 | def log(type, what)
14 | io = capture_io { described_class.new.api(what.to_json) }
15 | type == :both ? io : io[type]
16 | end
17 |
18 | #
19 |
20 | include_context :repos do
21 | describe "#output" do
22 | before do
23 | mocked_repo.with_opts("log_filters" => [
24 | /hello/
25 | ])
26 | end
27 |
28 | it "should be able to filter messages" do
29 | expect(described_class.new(mocked_repo.to_repo).send(:output, "hello world")).to(
30 | be_truthy
31 | )
32 | end
33 | end
34 | end
35 |
36 | #
37 |
38 | describe "#stdout" do
39 | it "should default to using .stdout" do
40 | expect(described_class.new.instance_variable_get(:@stdout)).to eq(
41 | described_class.stdout
42 | )
43 | end
44 |
45 | it "should be overrideable" do
46 | val = described_class.new(nil, "hello").instance_variable_get(:@stdout)
47 | expect(val).to eq(
48 | "hello"
49 | )
50 | end
51 | end
52 |
53 | #
54 |
55 | describe "#stderr" do
56 | it "should default to using .stderr" do
57 | expect(described_class.new.instance_variable_get(:@stderr)).to eq(
58 | described_class.stderr
59 | )
60 | end
61 |
62 | it "should be overrideable" do
63 | val = described_class.new(nil, nil, "world").
64 | instance_variable_get(:@stderr)
65 | expect(val).to eq(
66 | "world"
67 | )
68 | end
69 | end
70 |
71 | #
72 |
73 | describe "#progress_bar" do
74 | subject do
75 | log :stderr, {
76 | "progress" => "world",
77 | "id" => "hello"
78 | }
79 | end
80 |
81 | #
82 |
83 | it "should prefix with the id and then message" do
84 | expect(Simple::Ansi.strip(subject).strip).to eq(
85 | "hello: world"
86 | )
87 | end
88 |
89 | #
90 |
91 | context "when no ID is present" do
92 | subject do
93 | log :stdout, {
94 | "progress" => "world"
95 | }
96 | end
97 |
98 | it "should not log" do
99 | expect(subject).to(
100 | be_empty
101 | )
102 | end
103 | end
104 | end
105 |
106 | #
107 |
108 | describe "#api" do
109 | context "when it's a stream" do
110 | subject do
111 | log :stdout, {
112 | "stream" => "hello\nworld"
113 | }
114 | end
115 |
116 | #
117 |
118 | it "should output what it gets" do
119 | expect(subject).to eq(
120 | "hello\nworld\n"
121 | )
122 | end
123 | end
124 |
125 | #
126 |
127 | context "when a message is not handled" do
128 | subject do
129 | log :both, {
130 | "unknown" => "hello"
131 | }
132 | end
133 |
134 | #
135 |
136 | it "should spit out the inspect of the message" do
137 | expect(subject[:stdout].strip).to eq({
138 | "unknown" => "hello"
139 | }.to_json)
140 | end
141 |
142 | #
143 |
144 | it "should log an error message" do
145 | expect(subject[:stderr]).not_to(
146 | be_empty
147 | )
148 | end
149 | end
150 | end
151 | end
152 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/notify_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Notify do
7 | include_context :repos
8 |
9 | #
10 |
11 | describe "#alias" do
12 | it "should have some color" do
13 | capture = capture_io do
14 | subject.alias(
15 | mocked_repo.to_scratch
16 | )
17 | end
18 |
19 | expect(Simple::Ansi.has?(capture[:stderr])).to eq(
20 | true
21 | )
22 | end
23 | end
24 |
25 | #
26 |
27 | describe "#build" do
28 | it "should output the user, tag and repo" do
29 | capture = capture_io do
30 | subject.build(
31 | mocked_repo.to_repo
32 | )
33 | end
34 |
35 | expect(capture).to include({
36 | :stderr => %r!building:[:a-z\-_\s]+/default:latest!i
37 | })
38 | end
39 |
40 | #
41 |
42 | context "(rootfs: true)" do
43 | it "should output a rootfs image if told to" do
44 | capture = capture_io do
45 | subject.build(mocked_repo.to_repo, {
46 | rootfs: true
47 | })
48 | end
49 |
50 | #
51 |
52 | expect(capture).to include({
53 | :stderr => %r!building[:a-z\s]+/rootfs:default!i
54 | })
55 | end
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/parser_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Parser do
7 | include_context :repos
8 |
9 | #
10 |
11 | subject do
12 | described_class
13 | end
14 |
15 | #
16 |
17 | before do
18 | mocked_repo.init({
19 | :type => :normal
20 | })
21 | end
22 |
23 | #
24 |
25 | describe "#parse" do
26 | it "should output a set" do
27 | expect(subject.new.parse).to be_a(
28 | Array
29 | )
30 | end
31 |
32 | #
33 |
34 | context "when given a bad identifier" do
35 | it "should throw" do
36 | expect { subject.new(["invalid/user/repo:tag"]).parse }.to raise_error(
37 | Docker::Template::Error::BadRepoName
38 | )
39 | end
40 |
41 | #
42 |
43 | it "should throw" do
44 | expect { subject.new(["user/repo:tag:double_tag"]).parse }.to raise_error(
45 | Docker::Template::Error::BadRepoName
46 | )
47 | end
48 | end
49 | end
50 |
51 | #
52 |
53 | describe ".to_repo_hash" do
54 | context "when given a valid identifier" do
55 | specify do
56 | expect(subject.send(:to_repo_hash, "repo:tag")).to \
57 | include({
58 | "name" => "repo"
59 | })
60 | end
61 |
62 | #
63 |
64 | specify do
65 | expect(subject.send(:to_repo_hash, "repo:tag")).to \
66 | include({
67 | "tag" => "tag"
68 | })
69 | end
70 |
71 | #
72 |
73 | specify do
74 | # user/repo:tag
75 | expect(subject.send(:to_repo_hash, "user/repo:tag")).to \
76 | include({
77 | "user" => "user"
78 | })
79 | end
80 |
81 | #
82 |
83 | specify do
84 | expect(subject.send(:to_repo_hash, "user/repo:tag")).to \
85 | include({
86 | "name" => "repo"
87 | })
88 | end
89 |
90 | #
91 |
92 | specify do
93 | expect(subject.send(:to_repo_hash, "user/repo:tag")).to \
94 | include({
95 | "tag" => "tag"
96 | })
97 | end
98 |
99 | #
100 |
101 | specify do
102 | expect(subject.send(:to_repo_hash, "user/repo")).to \
103 | include({
104 | "user" => "user"
105 | })
106 | end
107 |
108 | #
109 |
110 | specify do
111 | expect(subject.send(:to_repo_hash, "user/repo")).to \
112 | include({
113 | "name" => "repo"
114 | })
115 | end
116 |
117 | #
118 |
119 | specify do
120 | expect(subject.send(:to_repo_hash, "repo")).to \
121 | include({
122 | "name" => "repo"
123 | })
124 | end
125 |
126 | #
127 |
128 | context do
129 | before do
130 | mocked_repo.with_opts({
131 | :tags => {
132 | "latest" => "normal"
133 | }
134 | })
135 | end
136 |
137 | #
138 |
139 | it "should output Templates" do
140 | expect(subject.new(%w(default)).parse.first).to be_a(
141 | Docker::Template::Repo
142 | )
143 | end
144 | end
145 | end
146 | end
147 |
148 | #
149 |
150 | describe ".full_name?" do
151 | context "when given repo/image:tag" do
152 | it "should return true" do
153 | expect(subject.full_name?("repo/image:tag")).to eq(
154 | true
155 | )
156 | end
157 | end
158 |
159 | #
160 |
161 | context "when given repo/image" do
162 | it "should return true" do
163 | expect(subject.full_name?("repo/image")).to eq(
164 | true
165 | )
166 | end
167 | end
168 |
169 | #
170 |
171 | context "when given repo:tag" do
172 | it "should return true" do
173 | expect(subject.full_name?("repo:tag")).to eq(
174 | true
175 | )
176 | end
177 | end
178 |
179 | #
180 |
181 | context "when given just a user/repo" do
182 | it "should return false" do
183 | expect(subject.full_name?("nothing")).to eq(
184 | false
185 | )
186 | end
187 | end
188 | end
189 |
190 | #
191 |
192 | describe "#all" do
193 | context "when given raw repos" do
194 | it "should return those" do
195 | expect(described_class.new(%w(hello)).all).to eq %w(
196 | hello
197 | )
198 | end
199 | end
200 |
201 | #
202 |
203 | context "when it's project" do
204 | before do
205 | allow(Docker::Template).to receive(:project?).and_return(
206 | true
207 | )
208 | end
209 |
210 | #
211 |
212 | subject do
213 | described_class.new
214 | end
215 |
216 | #
217 |
218 | it "should return the single root" do
219 | expect(subject.all.size).to eq 1
220 | expect(subject.all).to include(
221 | Docker::Template.root.basename.to_s
222 | )
223 | end
224 | end
225 | end
226 | end
227 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template/repo_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template::Repo do
7 | include_context :repos
8 |
9 | #
10 |
11 | subject do
12 | mocked_repo.to_repo
13 | end
14 |
15 | #
16 |
17 | it { is_expected.to respond_to :name }
18 | it { is_expected.to respond_to :tag }
19 | it { is_expected.to respond_to :type }
20 | it { is_expected.to respond_to :user }
21 | it { is_expected.to respond_to :to_h }
22 |
23 | #
24 |
25 | describe "#initialize" do
26 | context "when repo does not exist" do
27 | it "should throw" do
28 | expect { mocked_repo.empty.to_repo }.to raise_error(
29 | Docker::Template::Error::RepoNotFound
30 | )
31 | end
32 | end
33 | end
34 |
35 | #
36 |
37 | describe "#aliased" do
38 | before do
39 | mocked_repo.add_alias :world, :tag => :hello
40 | mocked_repo.add_alias :person, :tag => "randon/image:name"
41 | mocked_repo.add_alias :local, :tag => "default:tag"
42 | mocked_repo.add_tag :hello, :group => :world
43 | mocked_repo.with_repo_init({
44 | :tag => :world
45 | })
46 | end
47 |
48 | it "should pull out the aliased repo" do
49 | expect(mocked_repo.to_repo.aliased.tag).to eq(
50 | "hello"
51 | )
52 | end
53 |
54 | #
55 |
56 | context "when given a non-local full name alias" do
57 | before do
58 | mocked_repo.with_repo_init({
59 | :tag => :person
60 | })
61 | end
62 |
63 | #
64 |
65 | it "should not throw" do
66 | expect { mocked_repo.to_repo.aliased }.not_to(
67 | raise_error
68 | )
69 | end
70 | end
71 |
72 | #
73 |
74 | context "when given a local full name alias" do
75 | before do
76 | mocked_repo.with_repo_init({
77 | :tag => :local
78 | })
79 | end
80 |
81 | #
82 |
83 | it "should return the local repo" do
84 | expect(mocked_repo.to_repo.aliased).to be_a(
85 | Docker::Template::Repo
86 | )
87 | end
88 | end
89 | end
90 |
91 | #
92 |
93 | describe "#to_s" do
94 | context "when no type or type = :image" do
95 | context "without a user" do
96 | before do
97 | mocked_repo.with_opts({
98 | "user" => "hello"
99 | })
100 | end
101 |
102 | #
103 |
104 | it "should use the default user" do
105 | expect(mocked_repo.to_repo.to_s).to match(
106 | %r!\Ahello/[a-z]+:[a-z]+\Z!
107 | )
108 | end
109 | end
110 |
111 | #
112 |
113 | context "without a tag" do
114 | before do
115 | mocked_repo.with_opts({
116 | "tag" => "hello"
117 | })
118 | end
119 |
120 | #
121 |
122 | it "should use the default tag" do
123 | expect(mocked_repo.to_repo.to_s).to match(
124 | %r!\A[a-z\-_]+/[a-z]+:hello!
125 | )
126 | end
127 | end
128 | end
129 |
130 | context "when rootfs: true" do
131 | it "should use the repo name as the tag" do
132 | prefix = Docker::Template::Meta::DEFAULTS["local_prefix"]
133 | expect(mocked_repo.to_repo.to_s(rootfs: true)).to eq(
134 | "#{prefix}/rootfs:default"
135 | )
136 | end
137 | end
138 | end
139 |
140 | #
141 |
142 | describe "#copy_dir" do
143 | it "should be a pathname" do
144 | expect(mocked_repo.to_repo.copy_dir).to be_a(
145 | Pathutil
146 | )
147 | end
148 |
149 | #
150 |
151 | context "(*)" do
152 | it "should join arguments sent" do
153 | expect(mocked_repo.to_repo.copy_dir("world").basename.to_s).to eq(
154 | "world"
155 | )
156 | end
157 | end
158 | end
159 |
160 | #
161 |
162 | describe "#root" do
163 | it "should put me into pry" do
164 | expect(mocked_repo.to_repo.root.relative_path_from(Docker::Template.root).to_s).to eq(
165 | "repos/default"
166 | )
167 | end
168 | end
169 |
170 | #
171 |
172 | describe "#to_tag_h" do
173 | it "should include user/repo" do
174 | expect(mocked_repo.to_repo.to_tag_h).to \
175 | include({
176 | "repo" => match(%r!\A[a-z\-_]+/default!)
177 | })
178 | end
179 |
180 | #
181 |
182 | it "should include a tag" do
183 | expect(mocked_repo.to_repo.to_tag_h).to \
184 | include({
185 | "tag" => "latest"
186 | })
187 | end
188 | end
189 |
190 | #
191 |
192 | describe "#to_rootfs_h" do
193 | before do
194 | mocked_repo.init({
195 | :type => :scratch
196 | })
197 | end
198 |
199 | #
200 |
201 | it "should include prefix/rootfs" do
202 | prefix = Docker::Template::Meta::DEFAULTS["local_prefix"]
203 | expect(mocked_repo.to_repo.to_rootfs_h).to include({
204 | "repo" => match(%r!\A#{Regexp.escape(prefix)}/rootfs!)
205 | })
206 | end
207 |
208 | #
209 |
210 | it "should include a tag" do
211 | expect(mocked_repo.to_repo.to_rootfs_h).to \
212 | include({
213 | "tag" => "default"
214 | })
215 | end
216 | end
217 |
218 | #
219 |
220 | describe "#tmpdir" do
221 | it "should be a pathname" do
222 | expect(mocked_repo.to_repo.tmpdir.tap(&:rm_rf)).to be_a(
223 | Pathutil
224 | )
225 | end
226 |
227 | #
228 |
229 | it "should exist" do
230 | dir = mocked_repo.to_repo.tmpdir
231 | expect(dir).to exist
232 | dir.rm_rf
233 | end
234 |
235 | #
236 |
237 | context "(*prefixes)" do
238 | it "should add those prefixes" do
239 | expect(mocked_repo.to_repo.tmpdir("hello").tap(&:rm_rf).to_s).to match(
240 | %r!-hello-!
241 | )
242 | end
243 | end
244 | end
245 |
246 | #
247 |
248 | describe "#tmpfile" do
249 | it "should be a Pathutil" do
250 | expect(mocked_repo.to_repo.tmpfile.tap(&:rm_rf)).to be_a(
251 | Pathutil
252 | )
253 | end
254 |
255 | #
256 |
257 | it "should exist" do
258 | file = mocked_repo.to_repo.tmpfile
259 | expect(file).to exist
260 | file.rm_rf
261 | end
262 |
263 | #
264 |
265 | context "(*prefixes)" do
266 | it "should add those prefixes" do
267 | expect(mocked_repo.to_repo.tmpfile("hello").tap(&:rm_rf).to_s).to match(
268 | %r!-hello-!
269 | )
270 | end
271 | end
272 | end
273 |
274 | #
275 |
276 | describe "#to_repos" do
277 | context do
278 | before do
279 | mocked_repo.with_opts({
280 | "tags" => {
281 | "hello" => "world",
282 | "world" => "hello"
283 | }
284 | })
285 | end
286 |
287 | #
288 |
289 | it "should pull all tags as individual repos" do
290 | expect(mocked_repo.to_repo.to_repos.size).to eq(
291 | 2
292 | )
293 | end
294 | end
295 |
296 | #
297 |
298 | context do
299 | before do
300 | mocked_repo.with_repo_init({
301 | "tag" => "default"
302 | })
303 | end
304 |
305 | #
306 |
307 | it "should return all repos" do
308 | expect(mocked_repo.to_repo.to_repos.first).to be_a(
309 | Docker::Template::Repo
310 | )
311 | end
312 | end
313 |
314 | #
315 |
316 | context "when a tag is given" do
317 | before do
318 | mocked_repo.with_repo_init({
319 | "tag" => "default"
320 | })
321 | end
322 |
323 | #
324 |
325 | it "should only return the current repo" do
326 | expect(mocked_repo.to_repo.to_repos.size).to eq(
327 | 1
328 | )
329 | end
330 | end
331 |
332 | context "when the template is a project", :type => :project do
333 | before do
334 | allow(Docker::Template).to receive(:project?).and_return(
335 | true
336 | )
337 | end
338 |
339 | #
340 |
341 | it "should return itself as a set" do
342 | repo = mocked_repo.to_repo
343 | expect(repo.to_repos.size).to eq 1
344 | expect(repo.to_repos).to include(
345 | repo
346 | )
347 | end
348 | end
349 | end
350 |
351 | #
352 |
353 | describe "#meta" do
354 | it "should be a Meta" do
355 | expect(mocked_repo.to_repo.meta).to be_a(
356 | Docker::Template::Meta
357 | )
358 | end
359 | end
360 |
361 | #
362 |
363 | describe "#to_env" do
364 | it "should return a hash to you" do
365 | expect(mocked_repo.to_repo.to_env).to be_a(
366 | Docker::Template::Meta
367 | )
368 | end
369 |
370 | #
371 |
372 | context "(tar_gz: val)" do
373 | let :result do
374 | mocked_repo.to_repo.to_env({
375 | :tar_gz => "val"
376 | })
377 | end
378 |
379 | it "should include the tar_gz" do
380 | expect(result[:all]).to include({
381 | "TAR_GZ" => "val"
382 | })
383 | end
384 | end
385 |
386 | #
387 |
388 | context "copy_dir: val" do
389 | let :result do
390 | mocked_repo.to_repo.to_env({
391 | :copy_dir => "val"
392 | })
393 | end
394 |
395 | it "should include the copy_dir" do
396 | expect(result[:all]).to include({
397 | "COPY_DIR" => "val"
398 | })
399 | end
400 | end
401 | end
402 | end
403 |
--------------------------------------------------------------------------------
/spec/tests/lib/docker/template_spec.rb:
--------------------------------------------------------------------------------
1 | # Frozen-string-literal: true
2 | # Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
3 | # Encoding: utf-8
4 |
5 | require "rspec/helper"
6 | describe Docker::Template do
7 | it { is_expected.to respond_to :gem_root }
8 | it { is_expected.to respond_to :template_root }
9 | it { is_expected.to respond_to :root }
10 | it { is_expected.to respond_to :get }
11 |
12 | let :template do
13 | described_class
14 | end
15 |
16 | #
17 |
18 | [:gem_root, :template_root, :root].each do |val|
19 | describe "##{val}" do
20 | it "should be a Pathame" do
21 | expect(template.send(val)).to be_a(
22 | Pathutil
23 | )
24 | end
25 | end
26 | end
27 |
28 | #
29 |
30 | describe "#project?" do
31 | include_context :repos
32 | context "when laid out as a project repository", :type => :project do
33 | it "should return true" do
34 | expect(template.project?).to eq(
35 | true
36 | )
37 | end
38 | end
39 |
40 | #
41 |
42 | context "when it's not laid out as project" do
43 | it "should return false" do
44 | expect(template.project?).to(
45 | be_falsey
46 | )
47 | end
48 | end
49 | end
50 |
51 | #
52 |
53 | describe "#get" do
54 | context "when no data is given" do
55 | it "should still return a string" do
56 | expect(template.get(:rootfs)).to be_a(
57 | String
58 | )
59 | end
60 | end
61 |
62 | #
63 |
64 | context "when data is given" do
65 | it "should parse that data with ERB" do
66 | expect(template.get(:rootfs, :rootfs_base_img => "hello world")).to start_with(
67 | "FROM hello world\n"
68 | )
69 | end
70 | end
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/templates/rootfs.erb:
--------------------------------------------------------------------------------
1 | FROM <%= @rootfs_base_img %>
2 | COPY copy /
3 | ENTRYPOINT [ \
4 | "/usr/local/bin/mkimg" \
5 | ]
6 |
--------------------------------------------------------------------------------
/templates/rootfs/alpine.erb:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | [ "$DEBUG" ] && set -x
3 | set -e
4 |
5 | ROOTFS=$(mktemp -d)
6 | TMPDIR=$(mktemp -d)
7 |
8 | MIRROR_HOST="<%= @meta.mirror_host %>"
9 | [ "$MIRROR_HOST" ] || MIRROR_HOST="mirror.envygeeks.io"
10 | MIRRORS="\nhttp://$MIRROR_HOST/alpine/<%= @meta.release =~ /^[\d,\.]+$/ ? "v" : "" %><%= @meta.release %>/main"
11 | MIRRORS=$MIRRORS"\n@community http://$MIRROR_HOST/alpine/<%= @meta.release =~ /^[\d,\.]+$/ ? "v" : "" %><%= @meta.release %>/community"
12 | MIRRORS=$MIRRORS"\n@communityEdge http://$MIRROR_HOST/alpine/edge/community"
13 | MIRRORS=$MIRRORS"\n@testing http://$MIRROR_HOST/alpine/edge/testing"
14 | MIRRORS=$MIRRORS"\n@edge http://$MIRROR_HOST/alpine/edge/main"
15 | RELEASE_URL="http://$MIRROR_HOST/alpine/<%= @meta.release \
16 | =~ /^[\d,\.]+$/ ? "v" : "" %><%= @meta.release %>/main"
17 |
18 | cd $TMPDIR
19 | mkdir -p $ROOTFS/etc
20 | apk update --repository=$RELEASE_URL
21 | apk fetch --stdout --repository=$RELEASE_URL alpine-keys | \
22 | tar -xvzC $ROOTFS etc/
23 |
24 | # --
25 |
26 | apk --initdb --root=$ROOTFS --repository=$RELEASE_URL --update-cache add \
27 | apk-tools libc-utils alpine-baselayout alpine-keys busybox musl \
28 | busybox-suid alpine-conf
29 |
30 | # --
31 |
32 | apk --keys-dir=$ROOTFS/etc/apk/keys --root=$ROOTFS --repository=$RELEASE_URL \
33 | fetch --stdout alpine-base | \
34 | tar -xvz -C "$ROOTFS" etc/
35 |
36 | # --
37 |
38 | cd ~> /dev/null
39 | cp -R $COPY_DIR/* $ROOTFS 2> /dev/null || true
40 |
41 | # --
42 |
43 | cp /etc/resolv.conf $ROOTFS/etc/resolv.conf
44 | printf "$MIRRORS" > $ROOTFS/etc/apk/repositories
45 | cp /etc/hosts $ROOTFS/etc/hosts
46 |
47 | apk --root=$ROOTFS update
48 | apk --root=$ROOTFS add <%= @meta.packages %>
49 | apk del --root=$ROOTFS <%= @meta.package_cleanup %>
50 | apk del --root=$ROOTFS <%= @meta.package_deep_cleanup %>
51 | mv $ROOTFS/var/run/* $ROOTFS/run 2> /dev/null || true
52 | mv $ROOTFS/var/lock $ROOTFS/run 2> /dev/null || true
53 | rm -rf $ROOTFS/var/run $ROOTFS/var/lock
54 | ln -sf /run/lock $ROOTFS/var/lock
55 | ln -sf /run $ROOTFS/var/run
56 |
57 | # --
58 |
59 | <% if @meta.helpers? %>
60 | mkdir -p /usr/src
61 | cd /usr/src
62 |
63 | apk update
64 | apk add ca-certificates git
65 | git clone --verbose https://github.com/envygeeks/docker-helper.git
66 | cp -R docker-helper/src/* $ROOTFS/
67 | <% end %>
68 |
69 | # --
70 |
71 | $ROOTFS/usr/bin/cleanup $ROOTFS || true
72 | rm -rf $ROOTFS/etc/hosts
73 | rm -rf $ROOTFS/etc/resolv.conf
74 | tar -zf $TAR_GZ --numeric-owner -C $ROOTFS -c .
75 |
--------------------------------------------------------------------------------
/templates/rootfs/ubuntu.erb:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | [ "$DEBUG" ] && set -x
3 | set -e
4 |
5 | ROOTFS=$(mktemp -d)
6 | MIRROR_HOST="<%= @meta.mirror_host %>"
7 | [ "$MIRROR_HOST" ] || MIRROR_HOST="archive.ubuntu.com"
8 | PARTNER_URL="https://mirror.envygeeks.io/core-ubuntu"
9 | TMP=$(mktemp -d)
10 |
11 | # --
12 |
13 | cd $TMP
14 | apk update
15 | apk add wget gnupg ca-certificates \
16 | gawk sed
17 |
18 | # --
19 |
20 | gpg --keyserver keyserver.ubuntu.com --recv-keys 1A5D6C4C7DB87C81
21 | wget $PARTNER_URL/<%= @meta.release %>/SHA256SUMS.gpg
22 | wget $PARTNER_URL/<%= @meta.release %>/SHA256SUMS
23 | gpg --verify SHA256SUMS.gpg SHA256SUMS
24 |
25 | cat SHA256SUMS
26 | sha=$(cat SHA256SUMS | grep "amd64" | awk -F' *' '{ print $1 }')
27 | img=$(cat SHA256SUMS | grep "amd64" | awk -F' *' '{ print $2 }' \
28 | | sed -r 's/^\*//')
29 |
30 | wget --progress=bar "$PARTNER_URL/<%= @meta.release %>/$img"
31 | if [ "$(sha256sum $img | awk '{ print $1 }')" != "$sha" ]; then
32 | echo "Bailing, the SHA256sum did not match."
33 | fi
34 |
35 | # --
36 |
37 | tar xzf $img -C $ROOTFS
38 | cd -> /dev/null
39 |
40 | # --
41 |
42 | rm -rf $ROOTFS/etc/hosts
43 | rm -rf $ROOTFS/etc/resolv.conf
44 | cp /etc/resolv.conf $ROOTFS/etc/resolv.conf
45 | cp /etc/hosts $ROOTFS/etc/hosts
46 |
47 | # --
48 |
49 | chroot "$ROOTFS" sh -ec "dpkg-divert --local --rename --add /sbin/initctl"
50 | chroot "$ROOTFS" sh -ec "dpkg-divert --local --rename --add /usr/sbin/update-rc.d"
51 | ln -s /bin/true $ROOTFS/usr/sbin/update-rc.d
52 | ln -s /bin/true $ROOTFS/sbin/initctl
53 |
54 | # --
55 |
56 | sed -i 's/^#\s*\(deb.*universe\)$/\1/g' $ROOTFS/etc/apt/sources.list
57 | sed -i "s/archive\.ubuntu\.com/$MIRROR_HOST/g" $ROOTFS/etc/apt/sources.list
58 | chroot "$ROOTFS" sh -ec 'echo "debconf debconf/frontend select Noninteractive" | debconf-set-selections'
59 | echo 'Dpkg::Options { "--force-confdef"; "--force-confold"; }' > $ROOTFS/etc/apt/apt.conf.d/03confdef
60 | echo 'APT::Get::Install-Recommends "false"; APT::Get::Install-Suggests "false";' > \
61 | $ROOTFS/etc/apt/apt.conf.d/00norecommends
62 |
63 | # --
64 |
65 | echo "exit 101" > $ROOTFS/usr/sbin/policy-rc.d policy-rc.d
66 | echo 'APT::Get::Assume-Yes "true";' > $ROOTFS/etc/apt/apt.conf.d/01yes
67 | echo 'Apt::Get::Purge "true";' > $ROOTFS/etc/apt/apt.conf.d/02purge
68 | sed -ri '/^(deb-src\s+|$|#)/d' $ROOTFS/etc/apt/sources.list
69 | chmod uog+x $ROOTFS/usr/sbin/policy-rc.d
70 |
71 | chroot "$ROOTFS" sh -ec "{
72 | apt-get update
73 | apt-get install locales deborphan <%= @meta.packages %>
74 | echo 'Yes, do as I say!' | SUDO_FORCE_REMOVE=yes apt-get autoremove -f <%= @meta.package_cleanup %>
75 | echo 'Yes, do as I say!' | SUDO_FORCE_REMOVE=yes apt-get autoremove -f <%= @meta.package_deep_cleanup %>
76 | apt-get autoremove \$(deborphan --guess-all) deborphan
77 | apt-get autoremove
78 | apt-get autoclean
79 | apt-get clean
80 |
81 | rm -rf /tmp/remove
82 | }"
83 |
84 | # --
85 |
86 | <% if @meta.helpers? %>
87 | mkdir -p /usr/src
88 | cd /usr/src
89 |
90 | apk add ca-certificates git
91 | git clone --verbose https://github.com/envygeeks/docker-helper.git
92 | cp -R docker-helper/src/* $ROOTFS/
93 | <% end %>
94 |
95 | # --
96 |
97 | cp -R $COPY_DIR/* $ROOTFS
98 | cleanup $ROOTFS
99 |
100 | # --
101 |
102 | rm -rf $ROOTFS/etc/hosts
103 | rm -rf $ROOTFS/etc/resolv.conf
104 | tar -zf $TAR_GZ --numeric-owner -C $ROOTFS -c .
105 |
--------------------------------------------------------------------------------
/templates/scratch.erb:
--------------------------------------------------------------------------------
1 | FROM scratch
2 | MAINTAINER <%= @maintainer %>
3 | ADD <%= @tar_gz %> /
4 | <% if @entrypoint %>
5 | ENTRYPOINT ["<%= @entrypoint %>"]
6 | <% end %>
7 |
--------------------------------------------------------------------------------