docker-template 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +13 -0
- data/LICENSE +13 -0
- data/README.md +20 -0
- data/Rakefile +20 -0
- data/lib/docker/template.rb +86 -0
- data/lib/docker/template/alias.rb +28 -0
- data/lib/docker/template/ansi.rb +75 -0
- data/lib/docker/template/auth.rb +25 -0
- data/lib/docker/template/common.rb +126 -0
- data/lib/docker/template/config.rb +84 -0
- data/lib/docker/template/error.rb +20 -0
- data/lib/docker/template/error/bad_exit_status.rb +17 -0
- data/lib/docker/template/error/bad_repo_name.rb +15 -0
- data/lib/docker/template/error/invalid_repo_type.rb +16 -0
- data/lib/docker/template/error/invalid_targz_file.rb +15 -0
- data/lib/docker/template/error/no_rootfs_copy_dir.rb +15 -0
- data/lib/docker/template/error/no_rootfs_mkimg.rb +15 -0
- data/lib/docker/template/error/no_setup_context_found.rb +15 -0
- data/lib/docker/template/error/not_implemented.rb +15 -0
- data/lib/docker/template/error/repo_not_found.rb +16 -0
- data/lib/docker/template/interface.rb +119 -0
- data/lib/docker/template/metadata.rb +160 -0
- data/lib/docker/template/parser.rb +66 -0
- data/lib/docker/template/patches.rb +9 -0
- data/lib/docker/template/patches/array.rb +11 -0
- data/lib/docker/template/patches/hash.rb +76 -0
- data/lib/docker/template/patches/object.rb +9 -0
- data/lib/docker/template/patches/pathname.rb +46 -0
- data/lib/docker/template/patches/string.rb +9 -0
- data/lib/docker/template/repo.rb +180 -0
- data/lib/docker/template/rootfs.rb +75 -0
- data/lib/docker/template/routable.rb +28 -0
- data/lib/docker/template/scratch.rb +139 -0
- data/lib/docker/template/simple.rb +51 -0
- data/lib/docker/template/stream.rb +62 -0
- data/lib/docker/template/templates/rootfs.erb +8 -0
- data/lib/docker/template/templates/scratch.erb +7 -0
- data/lib/docker/template/util.rb +39 -0
- data/lib/docker/template/util/copy.rb +82 -0
- data/lib/docker/template/util/data.rb +26 -0
- data/lib/docker/template/version.rb +9 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5f3b7843f510727766a84aa47a990fa24e1d75db
|
4
|
+
data.tar.gz: 385b436c17f1ac1c3dcf71236f0d80c833945384
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4d0e8cb2c9bb3f852be270b072abf34b7cbc8b895d5e3e61c4191af9f21e6e7dacda55e2970b93722add97f0c41e5621d1d5f4a37239274b2ccfb106ced82dcf
|
7
|
+
data.tar.gz: f7615f982ea856ee912d46fe5e2973c0a6932f34d61b0f47c176928b01fb07a7ae928b6c2fdfaff116f25e80c1acb431fc96381ee98666ce0d33e28b41d20732
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Frozen-string-literal: true
|
2
|
+
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Encoding: utf-8
|
4
|
+
|
5
|
+
source "https://rubygems.org"
|
6
|
+
gem "rake", :require => false
|
7
|
+
gemspec
|
8
|
+
|
9
|
+
group :development do
|
10
|
+
gem "guard-rspec", :require => false
|
11
|
+
gem "benchmark-ips", :require => false
|
12
|
+
gem "pry", :require => false
|
13
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2015 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.
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
[][travis]
|
2
|
+
[][coveralls]
|
3
|
+
[][codeclimate]
|
4
|
+
|
5
|
+
[codeclimate]: https://codeclimate.com/github/envygeeks/docker-template
|
6
|
+
[coveralls]: https://coveralls.io/github/envygeeks/docker-template?branch=master
|
7
|
+
[travis]: https://travis-ci.org/envygeeks/docker-template
|
8
|
+
|
9
|
+
# Docker Template
|
10
|
+
|
11
|
+
Docker template is a way to organize your Docker repos into multiple types with
|
12
|
+
multiple templates for multiple tags but those templates aren't actually templates,
|
13
|
+
they are JSON or YAML files and copy folders with tons of shared or split data.
|
14
|
+
The idea is that you can have many tags for a single repo and that each tag
|
15
|
+
can have many different things going on.
|
16
|
+
|
17
|
+
Docker template makes it so that you can facilitate and organize those things in
|
18
|
+
a clean way and just get work done. It also makes building "scratch" images 100%
|
19
|
+
easier for anybody by doing most of the work for you and just asking that you
|
20
|
+
build the file system script that pumps out a tar.gz file.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Frozen-string-literal: true
|
2
|
+
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Encoding: utf-8
|
4
|
+
|
5
|
+
$:.unshift(File.expand_path("../lib", __FILE__))
|
6
|
+
require "rspec/core/rake_task"
|
7
|
+
task :default => [:spec]
|
8
|
+
RSpec::Core::RakeTask.new :spec
|
9
|
+
task :test => :spec
|
10
|
+
|
11
|
+
task :build do
|
12
|
+
exec "bundle", "exec", "bin/docker-template", *ARGV[
|
13
|
+
1..-1
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
task :pry do
|
18
|
+
exec "bundle", "exec", "pry", "-Ilib/", \
|
19
|
+
"-rdocker/template"
|
20
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# Frozen-string-literal: true
|
2
|
+
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Encoding: utf-8
|
4
|
+
|
5
|
+
require "docker/template/version"
|
6
|
+
require "docker/template/patches"
|
7
|
+
|
8
|
+
require "docker"
|
9
|
+
require "forwardable"
|
10
|
+
require "json"
|
11
|
+
require "erb"
|
12
|
+
|
13
|
+
module Docker
|
14
|
+
module Template module_function
|
15
|
+
autoload :Util, "docker/template/util"
|
16
|
+
autoload :Config, "docker/template/config"
|
17
|
+
autoload :Ansi, "docker/template/ansi"
|
18
|
+
autoload :Parser, "docker/template/parser"
|
19
|
+
autoload :Routable, "docker/template/routable"
|
20
|
+
autoload :Interface, "docker/template/interface"
|
21
|
+
autoload :Metadata, "docker/template/metadata"
|
22
|
+
autoload :Stream, "docker/template/stream"
|
23
|
+
autoload :Safe, "docker/template/safe"
|
24
|
+
autoload :Repo, "docker/template/repo"
|
25
|
+
autoload :Error, "docker/template/error"
|
26
|
+
autoload :Common, "docker/template/common"
|
27
|
+
autoload :Rootfs, "docker/template/rootfs"
|
28
|
+
autoload :Scratch, "docker/template/scratch"
|
29
|
+
autoload :Simple, "docker/template/simple"
|
30
|
+
autoload :Alias, "docker/template/alias"
|
31
|
+
autoload :Auth, "docker/template/auth"
|
32
|
+
|
33
|
+
def config
|
34
|
+
return @config ||= begin
|
35
|
+
Config.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
|
41
|
+
def root
|
42
|
+
return @root ||= begin
|
43
|
+
Pathname.new(Dir.pwd)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# The location of the standard repos/ dir, you can change this by adding
|
48
|
+
# `repos_dir` into your configuration file. I'm not saying it has to be but
|
49
|
+
# it should probably be relative rather than absolute, ther are no
|
50
|
+
# guarantees that an absolute path will work.
|
51
|
+
|
52
|
+
def repos_root
|
53
|
+
return @repos_root ||= begin
|
54
|
+
root.join(config["repos_dir"])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Provides the root to Docker template, wherever it is installed so that
|
59
|
+
# we can do things, mostly ignore files for the profiler. Otherwise it's
|
60
|
+
# not really used, it's just an encapsulator.
|
61
|
+
|
62
|
+
def gem_root
|
63
|
+
return @gem_root ||= begin
|
64
|
+
path = File.expand_path("../../", __dir__)
|
65
|
+
Pathname.new(path)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Provides the templates directory so you can quickly pull a template
|
70
|
+
# from our templates and use it if you wish to.
|
71
|
+
|
72
|
+
def template_root
|
73
|
+
return @template_root ||= begin
|
74
|
+
gem_root.join("lib/docker/template/templates")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
|
80
|
+
def get(name, data = {})
|
81
|
+
data = Util::Data.new(data)
|
82
|
+
template = template_root.join("#{name}.erb").read
|
83
|
+
ERB.new(template).result(data._binding)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Docker
|
2
|
+
module Template
|
3
|
+
class Alias
|
4
|
+
def initialize(aliased)
|
5
|
+
@aliased = aliased
|
6
|
+
end
|
7
|
+
|
8
|
+
#
|
9
|
+
|
10
|
+
def build
|
11
|
+
Util.notify_alias(@aliased)
|
12
|
+
prebuild unless @aliased.parent_img
|
13
|
+
@aliased.parent_img.tag(@aliased.repo.to_tag_h)
|
14
|
+
@aliased.push
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
|
19
|
+
private
|
20
|
+
def prebuild
|
21
|
+
repo = @aliased.parent_repo
|
22
|
+
simple = @aliased.repo.type == "simple"
|
23
|
+
@aliased.class.new(repo, @aliased.rootfs_img) unless simple
|
24
|
+
@aliased.class.new(repo).build if simple
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Frozen-string-literal: true
|
2
|
+
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Encoding: utf-8
|
4
|
+
|
5
|
+
module Docker
|
6
|
+
module Template
|
7
|
+
module Ansi module_function
|
8
|
+
Escape = "%c" % 27
|
9
|
+
Match = /#{Escape}\[(?:\d+)(?:;\d+)?(j|k|m|s|u|A|B)|\e\(B\e\[m/ix.freeze
|
10
|
+
Colors = { :red => 31, :green => 32, :black => 30, :magenta => 35,
|
11
|
+
:yellow => 33, :white => 37, :blue => 34, :cyan => 36 }
|
12
|
+
|
13
|
+
# Strip ANSI from the current string. It also strips cursor stuff,
|
14
|
+
# well some of it, and it also strips some other stuff that a lot of
|
15
|
+
# the other ANSI strippers don't.
|
16
|
+
|
17
|
+
def strip(str)
|
18
|
+
str.gsub Match, ""
|
19
|
+
end
|
20
|
+
|
21
|
+
# Reset the vterm view if it's supported. Depending on how badly
|
22
|
+
# your vterm is implemented it might reset rather than clear scrollback
|
23
|
+
# with a few empty lines added on the top.
|
24
|
+
|
25
|
+
def clear
|
26
|
+
$stdout.print("%c[H%c[2J" % [27, 27])
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
|
31
|
+
def has?(str)
|
32
|
+
!!(str =~ Match)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Jump the cursor up and then back down or just up and down. This is
|
36
|
+
# useful when streaming async downloads from something like Docker. It
|
37
|
+
# also works better than using `tput`
|
38
|
+
|
39
|
+
def jump(str = "", up: nil, down: nil, both: nil, clear: true)
|
40
|
+
str = clear_line(str) if clear
|
41
|
+
|
42
|
+
return "%c[%dA%s%c[%dB" % [27, up || both, str, 27, down || both] if (up && down) || both
|
43
|
+
up ? "%c[%dA%s" % [27, up, str] : "%s%c[%dB" % [str, 27, down]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Reset the color back to the default color so that you do not leak any
|
47
|
+
# colors when you move onto the next line. This is probably normally
|
48
|
+
# used as part of a wrapper so that we don't leak colors.
|
49
|
+
|
50
|
+
def reset(str = "")
|
51
|
+
@ansi_reset ||= "%c[0m" % 27
|
52
|
+
"#{@ansi_reset}#{str}"
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
|
57
|
+
def clear_line(str = "")
|
58
|
+
@ansi_clear_line ||= "%c[2K\r" % 27
|
59
|
+
"#{@ansi_clear_line}#{str}\r"
|
60
|
+
end
|
61
|
+
|
62
|
+
# SEE: `self::Colors` for a list of methods. They are mostly
|
63
|
+
# standard base colors supported by pretty much any xterm-color, we do
|
64
|
+
# not need more than the base colors so we do not include them.
|
65
|
+
# Actually... if I'm honest we don't even need most of the
|
66
|
+
# base colors.
|
67
|
+
|
68
|
+
Colors.each do |color, num|
|
69
|
+
define_method color do |str|
|
70
|
+
"#{"%c" % 27}[#{num}m#{str}#{reset}"
|
71
|
+
end; module_function color
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Docker
|
2
|
+
module Template
|
3
|
+
module Auth
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def auth!
|
7
|
+
return unless login = get_info
|
8
|
+
login["auths"].each do |server, auth|
|
9
|
+
username, password = Base64.decode64(auth["auth"]).split(":", 2)
|
10
|
+
Docker.authenticate!({
|
11
|
+
"username" => username,
|
12
|
+
"serveraddress" => server,
|
13
|
+
"email" => auth["email"],
|
14
|
+
"password" => password
|
15
|
+
})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_info
|
20
|
+
path = Pathname.new("~/.docker/config.json").expand_path
|
21
|
+
return JSON.parse(path.read) if path.exist?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Frozen-string-literal: true
|
2
|
+
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Encoding: utf-8
|
4
|
+
|
5
|
+
module Docker
|
6
|
+
module Template
|
7
|
+
class Common
|
8
|
+
CopyMethods = [
|
9
|
+
:setup_context, :copy_global,
|
10
|
+
:copy_all, :copy_type, :copy_tag, :build_context,
|
11
|
+
:verify_context].freeze
|
12
|
+
|
13
|
+
def push
|
14
|
+
return if rootfs? || !Interface.push?
|
15
|
+
|
16
|
+
Auth.auth!
|
17
|
+
img = @img || Docker::Image.get(@repo.to_s)
|
18
|
+
logger = Stream.new.method(:log)
|
19
|
+
img.push(&logger)
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
|
24
|
+
def aliased?
|
25
|
+
@repo.tag != @repo.aliased && !rootfs?
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
|
30
|
+
def rootfs?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
|
36
|
+
def parent_repo
|
37
|
+
return false unless aliased?
|
38
|
+
@parent_repo ||= Repo.new(@repo.to_h.merge({
|
39
|
+
"tag" => @repo.aliased
|
40
|
+
}))
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
|
45
|
+
def parent_img
|
46
|
+
return false unless aliased?
|
47
|
+
@parent_img ||= Docker::Image.get(parent_repo.to_s)
|
48
|
+
rescue Docker::Error::NotFoundError
|
49
|
+
if aliased?
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
|
56
|
+
def build
|
57
|
+
return Alias.new(self).build if aliased?
|
58
|
+
|
59
|
+
Ansi.clear
|
60
|
+
Util.notify_build(@repo, rootfs: rootfs?)
|
61
|
+
logger = Stream.new.method(:log)
|
62
|
+
copy_build_and_verify
|
63
|
+
|
64
|
+
Dir.chdir(@context) do
|
65
|
+
@img = Docker::Image.build_from_dir(".", &logger)
|
66
|
+
@img.tag rootfs?? @repo.to_rootfs_h : @repo.to_tag_h
|
67
|
+
push
|
68
|
+
end
|
69
|
+
rescue SystemExit => exit_
|
70
|
+
unlink img: true
|
71
|
+
raise exit_
|
72
|
+
ensure
|
73
|
+
if rootfs?
|
74
|
+
unlink img: false else unlink
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
|
80
|
+
private
|
81
|
+
def copy_build_and_verify
|
82
|
+
unless respond_to?(:setup_context, true)
|
83
|
+
raise Error::NoSetupContextFound
|
84
|
+
end
|
85
|
+
|
86
|
+
CopyMethods.each do |val|
|
87
|
+
send(val) if respond_to?(val, true)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def copy_tag
|
93
|
+
return if rootfs?
|
94
|
+
dir = @repo.copy_dir("tag", @repo.tag)
|
95
|
+
Util::Copy.directory(dir, @copy)
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
|
100
|
+
private
|
101
|
+
def copy_global
|
102
|
+
return if rootfs?
|
103
|
+
dir = Template.root.join(@repo.metadata["copy_dir"])
|
104
|
+
Util::Copy.directory(dir, @copy)
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
|
109
|
+
private
|
110
|
+
def copy_type
|
111
|
+
return unless !rootfs? && build_type = @repo.metadata["tags"][@repo.tag]
|
112
|
+
dir = @repo.copy_dir("type", build_type)
|
113
|
+
Util::Copy.directory(dir, @copy)
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
|
118
|
+
private
|
119
|
+
def copy_all
|
120
|
+
return if rootfs?
|
121
|
+
dir = @repo.copy_dir("all")
|
122
|
+
Util::Copy.directory(dir, @copy)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# Frozen-string-literal: true
|
2
|
+
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Encoding: utf-8
|
4
|
+
|
5
|
+
require "json"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
module Docker
|
9
|
+
module Template
|
10
|
+
|
11
|
+
# Configuration is a global version of meatadata, where anything
|
12
|
+
# that can be set on configuration can be optimized and stored globally
|
13
|
+
# in a opts.{json,yml} file in the current working directory.
|
14
|
+
|
15
|
+
class Config
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
def_delegator :@config, :keys
|
19
|
+
def_delegator :@config, :to_h
|
20
|
+
def_delegator :@config, :to_enum
|
21
|
+
def_delegator :@config, :has_key?
|
22
|
+
def_delegator :@config, :each
|
23
|
+
def_delegator :@config, :[]
|
24
|
+
|
25
|
+
Defaults = {
|
26
|
+
"type" => "simple",
|
27
|
+
"user" => "envygeeks",
|
28
|
+
"local_prefix" => "local",
|
29
|
+
"rootfs_base_img" => "envygeeks/ubuntu:tiny",
|
30
|
+
"maintainer" => "Jordon Bedwell <jordon@envygeeks.io>",
|
31
|
+
"dockerhub_copy" => false,
|
32
|
+
"repos_dir" => "repos",
|
33
|
+
"copy_dir" => "copy",
|
34
|
+
"tag" => "latest",
|
35
|
+
|
36
|
+
"aliases" => {},
|
37
|
+
"pkgs" => { "tag" => {}, "type" => {}, "all" => nil },
|
38
|
+
"entries" => { "tag" => {}, "type" => {}, "all" => nil },
|
39
|
+
"releases" => { "tag" => {}, "type" => {}, "all" => nil },
|
40
|
+
"versions" => { "tag" => {}, "type" => {}, "all" => nil },
|
41
|
+
"env" => { "tag" => {}, "type" => {}, "all" => nil },
|
42
|
+
"tags" => {}
|
43
|
+
}.freeze
|
44
|
+
|
45
|
+
EmptyDefaults = {
|
46
|
+
"tags" => { "latest" => "normal" }
|
47
|
+
}
|
48
|
+
|
49
|
+
#
|
50
|
+
|
51
|
+
def initialize
|
52
|
+
@config = Defaults.deep_merge(read_config_from)
|
53
|
+
@config = @config.merge(EmptyDefaults) do |key, oval, nval|
|
54
|
+
oval.nil? || oval.empty?? nval : oval
|
55
|
+
end
|
56
|
+
|
57
|
+
@config.freeze
|
58
|
+
end
|
59
|
+
|
60
|
+
# Allows you to read a configuration file from a root and get back
|
61
|
+
# either the parsed data or a blank hash that can be merged the way you
|
62
|
+
# wish to merge it (if you even care to merge it.)
|
63
|
+
|
64
|
+
def read_config_from(dir = Docker::Template.root)
|
65
|
+
file = Dir[dir.join("*.{json,yml}")].first
|
66
|
+
return {} unless file && (file = Pathname.new(file)).file?
|
67
|
+
return JSON.parse(file.read).stringify if file.extname == ".json"
|
68
|
+
YAML.load_file(file).stringify
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
|
73
|
+
def has_default?(key)
|
74
|
+
return @config.has_key?(key)
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
|
79
|
+
def build_types
|
80
|
+
return @build_types ||= %W(simple scratch).freeze
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|