docker-template 0.2.0 → 0.3.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 +4 -4
- data/Gemfile +30 -4
- data/LICENSE +1 -1
- data/README.md +79 -14
- data/Rakefile +115 -38
- data/bin/docker-template +24 -10
- data/comp/bin +9 -0
- data/comp/list +83 -0
- data/comp/list.pak +0 -0
- data/lib/docker/template.rb +47 -61
- data/lib/docker/template/builder.rb +302 -0
- data/lib/docker/template/cache.rb +71 -0
- data/lib/docker/template/cli.rb +125 -0
- data/lib/docker/template/error.rb +120 -11
- data/lib/docker/template/logger.rb +128 -0
- data/lib/docker/template/metadata.rb +566 -103
- data/lib/docker/template/normal.rb +46 -0
- data/lib/docker/template/notify.rb +44 -0
- data/lib/docker/template/parser.rb +48 -38
- data/lib/docker/template/repo.rb +131 -97
- data/lib/docker/template/rootfs.rb +51 -41
- data/lib/docker/template/scratch.rb +96 -66
- data/lib/docker/template/version.rb +4 -2
- data/lib/erb/context.rb +29 -0
- data/shas.yml +11 -0
- data/templates/rootfs.erb +5 -0
- data/templates/rootfs/alpine.erb +71 -0
- data/templates/rootfs/ubuntu.erb +76 -0
- data/{lib/docker/template/templates → templates}/scratch.erb +0 -1
- metadata +64 -50
- data/lib/docker/template/alias.rb +0 -28
- data/lib/docker/template/ansi.rb +0 -85
- data/lib/docker/template/auth.rb +0 -25
- data/lib/docker/template/common.rb +0 -130
- data/lib/docker/template/config.rb +0 -80
- data/lib/docker/template/error/bad_exit_status.rb +0 -17
- data/lib/docker/template/error/bad_repo_name.rb +0 -15
- data/lib/docker/template/error/invalid_repo_type.rb +0 -16
- data/lib/docker/template/error/invalid_targz_file.rb +0 -15
- data/lib/docker/template/error/no_rootfs_copy_dir.rb +0 -15
- data/lib/docker/template/error/no_rootfs_mkimg.rb +0 -15
- data/lib/docker/template/error/no_setup_context_found.rb +0 -15
- data/lib/docker/template/error/not_implemented.rb +0 -15
- data/lib/docker/template/error/repo_not_found.rb +0 -16
- data/lib/docker/template/interface.rb +0 -118
- data/lib/docker/template/patches.rb +0 -9
- data/lib/docker/template/patches/array.rb +0 -11
- data/lib/docker/template/patches/hash.rb +0 -71
- data/lib/docker/template/patches/object.rb +0 -9
- data/lib/docker/template/patches/pathname.rb +0 -46
- data/lib/docker/template/patches/string.rb +0 -9
- data/lib/docker/template/routable.rb +0 -28
- data/lib/docker/template/simple.rb +0 -49
- data/lib/docker/template/stream.rb +0 -63
- data/lib/docker/template/templates/rootfs.erb +0 -8
- data/lib/docker/template/util.rb +0 -54
- data/lib/docker/template/util/copy.rb +0 -77
- data/lib/docker/template/util/data.rb +0 -26
@@ -0,0 +1,125 @@
|
|
1
|
+
# ----------------------------------------------------------------------------
|
2
|
+
# Frozen-string-literal: true
|
3
|
+
# Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
|
4
|
+
# Encoding: utf-8
|
5
|
+
# ----------------------------------------------------------------------------
|
6
|
+
|
7
|
+
require "thor"
|
8
|
+
|
9
|
+
module Docker
|
10
|
+
module Template
|
11
|
+
class CLI < Thor
|
12
|
+
|
13
|
+
# ----------------------------------------------------------------------
|
14
|
+
# docker-template build [repos [opts]]
|
15
|
+
# ----------------------------------------------------------------------
|
16
|
+
|
17
|
+
desc "build [REPOS [OPTS]]", "Build all (or some) of your repositories"
|
18
|
+
option :cache_only, :type => :boolean, :desc => "Only cache your repositories, don't build."
|
19
|
+
option :clean_only, :type => :boolean, :desc => "Only clean your repositories, don't build."
|
20
|
+
option :push_only, :type => :boolean, :desc => "Only push your repositories, don't build."
|
21
|
+
option :profile, :type => :boolean, :desc => "Profile Memory."
|
22
|
+
option :tty, :type => :boolean, :desc => "Enable TTY Output."
|
23
|
+
option :push, :type => :boolean, :desc => "Push Repo After Building."
|
24
|
+
option :cache, :type => :boolean, :desc => "Cache your repositories to cache."
|
25
|
+
option :mocking, :type => :boolean, :desc => "Disable Certain Actions."
|
26
|
+
option :clean, :type => :boolean, :desc => "Cleanup your caches."
|
27
|
+
|
28
|
+
# ----------------------------------------------------------------------
|
29
|
+
|
30
|
+
def build(*args)
|
31
|
+
repos = nil; with_profiling do
|
32
|
+
repos = Parser.new(args, options).parse.tap { |o| o.map( \
|
33
|
+
&:build) }.uniq(&:name).map(&:clean)
|
34
|
+
end
|
35
|
+
|
36
|
+
rescue Docker::Template::Error::StandardError => e
|
37
|
+
$stderr.puts Simple::Ansi.red(e.message)
|
38
|
+
exit e.respond_to?(:status) ? e.status : 1
|
39
|
+
end
|
40
|
+
|
41
|
+
# ----------------------------------------------------------------------
|
42
|
+
# docker-template list [options]
|
43
|
+
# ----------------------------------------------------------------------
|
44
|
+
|
45
|
+
desc "list [OPTS]", "List all possible builds."
|
46
|
+
option :grep, :type => :boolean, :desc => "Make --only a Regexp search."
|
47
|
+
option :only, :type => :string, :desc => "Only a specific repo."
|
48
|
+
|
49
|
+
# ----------------------------------------------------------------------
|
50
|
+
|
51
|
+
def list
|
52
|
+
Parser.new([], {}).parse.each do |repo|
|
53
|
+
repo_s = repo_s = repo.to_s.gsub(/^[^\/]+\//, "")
|
54
|
+
next unless (only.is_a?(Regexp) && repo_s =~ only) \
|
55
|
+
|| (only && repo_s == only) || !only
|
56
|
+
|
57
|
+
$stderr.print repo.to_s
|
58
|
+
$stderr.print " -> ", repo.aliased.to_s, "\n" if repo.alias?
|
59
|
+
$stderr.puts unless repo.alias?
|
60
|
+
end
|
61
|
+
rescue Docker::Template::Error::StandardError => e
|
62
|
+
$stderr.puts Simple::Ansi.red(e.message)
|
63
|
+
exit e.respond_to?(:status) \
|
64
|
+
? e.status : 1
|
65
|
+
end
|
66
|
+
|
67
|
+
# ----------------------------------------------------------------------
|
68
|
+
|
69
|
+
no_tasks do
|
70
|
+
def only
|
71
|
+
return @only ||= begin
|
72
|
+
if !options.grep?
|
73
|
+
then options[
|
74
|
+
:only
|
75
|
+
]
|
76
|
+
|
77
|
+
elsif options.only?
|
78
|
+
Regexp.new(options[
|
79
|
+
:only
|
80
|
+
])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# --------------------------------------------------------------------
|
86
|
+
# When a user wishes to profile their builds to see memory being used.
|
87
|
+
# rubocop:disable Lint/RescueException
|
88
|
+
# --------------------------------------------------------------------
|
89
|
+
|
90
|
+
def with_profiling
|
91
|
+
if options.profile?
|
92
|
+
begin
|
93
|
+
require "memory_profiler"
|
94
|
+
MemoryProfiler.report(:top => 10_240) { yield }.pretty_print({\
|
95
|
+
:to_file => "mem.txt"
|
96
|
+
})
|
97
|
+
rescue LoadError
|
98
|
+
$stderr.puts "The gem 'memory_profiler' wasn't found."
|
99
|
+
$stderr.puts "You can install it with `gem install memory_profiler'"
|
100
|
+
abort "Hope you install it so you can report back."
|
101
|
+
end
|
102
|
+
|
103
|
+
else
|
104
|
+
yield
|
105
|
+
end
|
106
|
+
|
107
|
+
rescue Excon::Errors::SocketError
|
108
|
+
$stderr.puts "Unable to connect to your Docker Instance."
|
109
|
+
$stderr.puts "Are you absolutely sure that you have the Docker installed?"
|
110
|
+
abort "Unable to build your images."
|
111
|
+
|
112
|
+
rescue Exception
|
113
|
+
raise unless $ERROR_POSITION
|
114
|
+
$ERROR_POSITION.delete_if do |source|
|
115
|
+
source =~ %r!#{Regexp.escape(
|
116
|
+
__FILE__
|
117
|
+
)}!o
|
118
|
+
end
|
119
|
+
|
120
|
+
raise
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,20 +1,129 @@
|
|
1
|
+
# ----------------------------------------------------------------------------
|
1
2
|
# Frozen-string-literal: true
|
2
|
-
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
|
3
4
|
# Encoding: utf-8
|
5
|
+
# ----------------------------------------------------------------------------
|
4
6
|
|
5
7
|
module Docker
|
6
8
|
module Template
|
7
9
|
module Error
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
StandardError = Class.new(
|
11
|
+
StandardError
|
12
|
+
)
|
13
|
+
|
14
|
+
# ----------------------------------------------------------------------
|
15
|
+
|
16
|
+
class PlaceHolderError < StandardError
|
17
|
+
def initialize(error)
|
18
|
+
super "PLACEHOLDER ERROR: %s" % (
|
19
|
+
error
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# ----------------------------------------------------------------------
|
25
|
+
|
26
|
+
class BadExitStatus < StandardError
|
27
|
+
attr_reader :status
|
28
|
+
|
29
|
+
def initialize(status)
|
30
|
+
super "Got bad exit status #{
|
31
|
+
@status = status
|
32
|
+
}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# ----------------------------------------------------------------------
|
37
|
+
|
38
|
+
class BadRepoName < StandardError
|
39
|
+
def initialize(name)
|
40
|
+
super "Only a-z0-9_- are allowed. Invalid repo name: #{
|
41
|
+
name
|
42
|
+
}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# ----------------------------------------------------------------------
|
47
|
+
|
48
|
+
class InvalidRepoType < StandardError
|
49
|
+
def initialize(type)
|
50
|
+
build_types = Template.config.build_types.join(", ")
|
51
|
+
super "Uknown repo type given '#{type}' not in '#{
|
52
|
+
build_types
|
53
|
+
}'"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# ----------------------------------------------------------------------
|
58
|
+
|
59
|
+
class InvalidTargzFile < StandardError
|
60
|
+
def initialize(tar_gz)
|
61
|
+
super "No data was given to the tar.gz file '#{
|
62
|
+
tar_gz.basename
|
63
|
+
}'"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# ----------------------------------------------------------------------
|
68
|
+
|
69
|
+
class InvalidYAMLFile < StandardError
|
70
|
+
def initialize(file)
|
71
|
+
super "The yaml data provided by #{file} is invalid and not a hash."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# ----------------------------------------------------------------------
|
76
|
+
|
77
|
+
class NoHookExists < StandardError
|
78
|
+
def initialize(base, point)
|
79
|
+
super "Unknown hook base '#{base}' or hook point '#{
|
80
|
+
point
|
81
|
+
}'"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# ----------------------------------------------------------------------
|
86
|
+
|
87
|
+
class NoRootMetadata < StandardError
|
88
|
+
def initialize
|
89
|
+
super "Metadata without the root flag must provide the root_metadata."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# ----------------------------------------------------------------------
|
94
|
+
|
95
|
+
class NoRootfsMkimg < StandardError
|
96
|
+
def initialize
|
97
|
+
super "Unable to find rootfs.rb in your repo folder."
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# ----------------------------------------------------------------------
|
102
|
+
|
103
|
+
class NoSetupContext < StandardError
|
104
|
+
def initialize
|
105
|
+
super "No #setup_context method exists."
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# ----------------------------------------------------------------------
|
110
|
+
|
111
|
+
class NotImplemented < StandardError
|
112
|
+
def initialize
|
113
|
+
super "The feature is not implemented yet, sorry about that."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# ----------------------------------------------------------------------
|
118
|
+
|
119
|
+
class RepoNotFound < StandardError
|
120
|
+
def initialize(repo = nil)
|
121
|
+
ending = repo ? "the repo '#{repo}'" : "your repo folder"
|
122
|
+
super "Unable to find #{
|
123
|
+
ending
|
124
|
+
}"
|
125
|
+
end
|
126
|
+
end
|
18
127
|
end
|
19
128
|
end
|
20
129
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# ----------------------------------------------------------------------------
|
2
|
+
# Frozen-string-literal: true
|
3
|
+
# Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
|
4
|
+
# Encoding: utf-8
|
5
|
+
# ----------------------------------------------------------------------------
|
6
|
+
|
7
|
+
module Docker
|
8
|
+
module Template
|
9
|
+
class Logger
|
10
|
+
def initialize(builder = nil)
|
11
|
+
@lines = { 0 => 0 }
|
12
|
+
@builder = \
|
13
|
+
builder
|
14
|
+
end
|
15
|
+
|
16
|
+
# ----------------------------------------------------------------------
|
17
|
+
|
18
|
+
def increment
|
19
|
+
@lines.update({
|
20
|
+
@lines.size => @lines.size
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
# ----------------------------------------------------------------------
|
25
|
+
# A simple TTY stream that just prints out the data that it is given.
|
26
|
+
# This is the logger that most will use for most of their building.
|
27
|
+
# ----------------------------------------------------------------------
|
28
|
+
|
29
|
+
def tty(stream)
|
30
|
+
$stdout.print stream
|
31
|
+
end
|
32
|
+
|
33
|
+
# ----------------------------------------------------------------------
|
34
|
+
# A simple logger that accepts a multi-type stream.
|
35
|
+
# ----------------------------------------------------------------------
|
36
|
+
|
37
|
+
def simple(type, str)
|
38
|
+
type == :stderr ? $stderr.print(str) : $stdout.print(str)
|
39
|
+
end
|
40
|
+
|
41
|
+
# ----------------------------------------------------------------------
|
42
|
+
# A more complex streamer designed for the actual output of the Docker.
|
43
|
+
# ----------------------------------------------------------------------
|
44
|
+
|
45
|
+
def api(part, *_)
|
46
|
+
stream = JSON.parse(part)
|
47
|
+
retried ||= false
|
48
|
+
|
49
|
+
return progress_bar(stream) if stream.any_key?("progress", "progressDetail")
|
50
|
+
return output(stream["status"] || stream["stream"]) if stream.any_key?("status", "stream")
|
51
|
+
return progress_error(stream) if stream.any_key?("errorDetail", "error")
|
52
|
+
|
53
|
+
warn Simple::Ansi.red("Unhandled Stream.")
|
54
|
+
$stdout.puts(
|
55
|
+
part
|
56
|
+
)
|
57
|
+
|
58
|
+
# Addresses a Docker 1.9 bug.
|
59
|
+
rescue JSON::ParserError => e
|
60
|
+
if !retried
|
61
|
+
retried = true
|
62
|
+
part = "#{part}\" }"
|
63
|
+
retry
|
64
|
+
else
|
65
|
+
raise e
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# ----------------------------------------------------------------------
|
70
|
+
|
71
|
+
def output(msg)
|
72
|
+
unless filter_matches?(msg)
|
73
|
+
$stdout.puts msg
|
74
|
+
increment
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# ----------------------------------------------------------------------
|
79
|
+
|
80
|
+
def progress_error(stream)
|
81
|
+
abort Object::Simple::Ansi.red(
|
82
|
+
stream["errorDetail"]["message"]
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
# ----------------------------------------------------------------------
|
87
|
+
|
88
|
+
private
|
89
|
+
def progress_bar(stream)
|
90
|
+
id = stream["id"]
|
91
|
+
|
92
|
+
return unless id
|
93
|
+
before, diff = progress_diff(id)
|
94
|
+
$stderr.print before if before
|
95
|
+
str = stream["progress"] || stream["status"]
|
96
|
+
str = "#{id}: #{str}\r"
|
97
|
+
|
98
|
+
$stderr.print(Object::Simple::Ansi.jump(
|
99
|
+
str, diff
|
100
|
+
))
|
101
|
+
end
|
102
|
+
|
103
|
+
# ----------------------------------------------------------------------
|
104
|
+
|
105
|
+
private
|
106
|
+
def progress_diff(id)
|
107
|
+
if @lines.key?(id)
|
108
|
+
return nil, @lines.size - @lines[id]
|
109
|
+
end
|
110
|
+
|
111
|
+
@lines[id] = @lines.size
|
112
|
+
before = "\n" unless @lines.one?
|
113
|
+
return before, 0
|
114
|
+
end
|
115
|
+
|
116
|
+
# ----------------------------------------------------------------------
|
117
|
+
|
118
|
+
private
|
119
|
+
def filter_matches?(msg)
|
120
|
+
return false unless @builder
|
121
|
+
|
122
|
+
@builder.repo.metadata["log_filters"].any? do |filter|
|
123
|
+
filter.is_a?(Regexp) && msg =~ filter || msg == filter
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -1,164 +1,627 @@
|
|
1
|
+
# ----------------------------------------------------------------------------
|
1
2
|
# Frozen-string-literal: true
|
2
|
-
# Copyright: 2015 Jordon Bedwell - Apache v2.0 License
|
3
|
+
# Copyright: 2015 - 2016 Jordon Bedwell - Apache v2.0 License
|
3
4
|
# Encoding: utf-8
|
5
|
+
# ----------------------------------------------------------------------------
|
6
|
+
|
7
|
+
require "active_support/inflector"
|
8
|
+
require "active_support/core_ext/hash/indifferent_access"
|
9
|
+
require "yaml"
|
4
10
|
|
5
11
|
module Docker
|
6
12
|
module Template
|
7
13
|
class Metadata
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def_delegator :@metadata, :keys
|
22
|
-
def_delegator :@metadata, :size
|
23
|
-
def_delegator :@metadata, :to_enum
|
24
|
-
def_delegator :@metadata, :has_key?
|
25
|
-
def_delegator :@metadata, :inspect
|
26
|
-
def_delegator :@metadata, :delete
|
27
|
-
def_delegator :@metadata, :each
|
28
|
-
def_delegator :@metadata, :to_h
|
29
|
-
def_delegator :@metadata, :key?
|
30
|
-
route_to_ivar :is_root, :@is_root, bool: true
|
31
|
-
route_to_hash :for_all, :self, :all
|
32
|
-
|
33
|
-
def initialize(metadata, root_metadata = metadata)
|
34
|
-
@is_root = metadata == root_metadata
|
35
|
-
@root_metadata = root_metadata || {}
|
36
|
-
@metadata = metadata || {}
|
37
|
-
|
38
|
-
return unless is_root?
|
39
|
-
@root_metadata = @metadata
|
40
|
-
@base = Template.config
|
14
|
+
attr_reader :data
|
15
|
+
extend Forwardable::Extended
|
16
|
+
|
17
|
+
# ----------------------------------------------------------------------
|
18
|
+
# rubocop:disable Style/MultilineBlockLayout
|
19
|
+
# ----------------------------------------------------------------------
|
20
|
+
|
21
|
+
OPTS_FILE = "opts.yml"
|
22
|
+
[Pathutil.allowed[:yaml][:classes], Array.allowed[:keys], \
|
23
|
+
Hash.allowed[:vals]].each do |v| v.push(
|
24
|
+
self, HashWithIndifferentAccess, Regexp
|
25
|
+
)
|
41
26
|
end
|
42
27
|
|
28
|
+
# ----------------------------------------------------------------------
|
29
|
+
# rubocop:enable Style/MultilineBlockLayout
|
30
|
+
# ----------------------------------------------------------------------
|
31
|
+
|
32
|
+
DEFAULTS = HashWithIndifferentAccess.new({
|
33
|
+
"log_filters" => [],
|
34
|
+
"push" => false,
|
35
|
+
"cache" => false,
|
36
|
+
"type" => "normal",
|
37
|
+
"user" => "random_user",
|
38
|
+
"local_prefix" => "local",
|
39
|
+
"rootfs_base_img" => "envygeeks/ubuntu",
|
40
|
+
"maintainer" => "Random User <random.user@example.com>",
|
41
|
+
"name" => Template.root.basename.to_s,
|
42
|
+
"rootfs_template" => "alpine",
|
43
|
+
"cache_dir" => "cache",
|
44
|
+
"repos_dir" => "repos",
|
45
|
+
"copy_dir" => "copy",
|
46
|
+
"tag" => "latest",
|
47
|
+
"clean" => true,
|
48
|
+
"tty" => false,
|
49
|
+
"tags" => {}
|
50
|
+
}).freeze
|
51
|
+
|
52
|
+
# ----------------------------------------------------------------------
|
53
|
+
# @param data [Hash, self.class] - the main data.
|
54
|
+
# @param root [Hash, self.class] - the root data.
|
55
|
+
# Create a new instance of `self.class`.
|
43
56
|
#
|
57
|
+
# @example ```
|
58
|
+
# self.class.new({
|
59
|
+
# :hello => :world
|
60
|
+
# })
|
61
|
+
# ```
|
62
|
+
# ----------------------------------------------------------------------
|
63
|
+
|
64
|
+
def initialize(overrides, root: nil)
|
65
|
+
if root.is_a?(self.class)
|
66
|
+
then root = root.to_h({
|
67
|
+
:raw => true
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
if overrides.is_a?(self.class)
|
72
|
+
then overrides = overrides.to_h({
|
73
|
+
:raw => true
|
74
|
+
})
|
75
|
+
end
|
44
76
|
|
45
|
-
|
46
|
-
|
77
|
+
if root.nil?
|
78
|
+
overrides = overrides.stringify
|
79
|
+
gdata = Template.root.join(OPTS_FILE).read_yaml
|
80
|
+
@data = DEFAULTS.deep_merge(gdata.stringify).deep_merge(overrides)
|
81
|
+
tdata = Template.root.join(@data[:repos_dir], @data[:name], OPTS_FILE).read_yaml
|
82
|
+
@data = @data.deep_merge(tdata.stringify).deep_merge(overrides)
|
83
|
+
@data = @data.stringify.with_indifferent_access
|
84
|
+
|
85
|
+
else
|
86
|
+
@data = overrides.stringify.with_indifferent_access
|
87
|
+
@root_data = root.stringify \
|
88
|
+
.with_indifferent_access
|
89
|
+
end
|
47
90
|
end
|
48
91
|
|
49
|
-
#
|
92
|
+
# ----------------------------------------------------------------------
|
93
|
+
|
94
|
+
def _shas
|
95
|
+
return @_shas ||= begin
|
96
|
+
self.class.new(Template.gem_root.join("shas.yml").read_yaml, {
|
97
|
+
:root => root_data
|
98
|
+
})
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# ----------------------------------------------------------------------
|
103
|
+
|
104
|
+
def root_data
|
105
|
+
return @root_data || @data
|
106
|
+
end
|
107
|
+
|
108
|
+
# ----------------------------------------------------------------------
|
109
|
+
|
110
|
+
def root
|
111
|
+
Template.root.join(
|
112
|
+
root_data[:repos_dir], root_data[:name]
|
113
|
+
)
|
114
|
+
end
|
50
115
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
116
|
+
# ----------------------------------------------------------------------
|
117
|
+
# Check if a part of the hash or a value is inside.
|
118
|
+
# @param val [Anytning(), Hash] - The key or key => val you wish check.
|
119
|
+
# @example metadata.include?(:key => :val) => true|false
|
120
|
+
# @example metadata.include?(:key) => true|false
|
121
|
+
# ----------------------------------------------------------------------
|
122
|
+
|
123
|
+
def include?(val)
|
124
|
+
if val.is_a?(Hash)
|
125
|
+
then val.stringify.each do |k, v|
|
126
|
+
unless @data.key?(k) && @data[k] == v
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
true
|
132
|
+
else
|
133
|
+
@data.include?(
|
134
|
+
val
|
135
|
+
)
|
136
|
+
end
|
56
137
|
end
|
57
138
|
|
58
|
-
#
|
59
|
-
#
|
139
|
+
# ----------------------------------------------------------------------
|
140
|
+
# @param key [Anything()] the key you wish to pull.
|
141
|
+
# @note we make the getter slightly more indifferent because of tags.
|
142
|
+
# Pull an indifferent key from the hash.
|
143
|
+
# ----------------------------------------------------------------------
|
60
144
|
|
61
145
|
def [](key)
|
62
|
-
|
63
|
-
|
146
|
+
val = begin
|
147
|
+
if key =~ /^\d+\.\d+$/
|
148
|
+
@data[key] || @data[
|
149
|
+
key.to_f
|
150
|
+
]
|
151
|
+
|
152
|
+
elsif key =~ /^\d+$/
|
153
|
+
@data[key] || @data[
|
154
|
+
key.to_i
|
155
|
+
]
|
156
|
+
|
157
|
+
else
|
158
|
+
@data[key]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
if val.is_a?(Hash)
|
163
|
+
return self.class.new(val, {
|
164
|
+
:root => root_data
|
165
|
+
})
|
166
|
+
end
|
64
167
|
|
65
|
-
return try_default(key) if !val && is_root?
|
66
|
-
return self.class.new(val, @root_metadata) if val.is_a?(Hash)
|
67
168
|
val
|
68
169
|
end
|
69
170
|
|
70
|
-
#
|
171
|
+
# ----------------------------------------------------------------------
|
71
172
|
|
72
|
-
def
|
73
|
-
|
173
|
+
def []=(key, val)
|
174
|
+
hash = { key => val }.stringify
|
175
|
+
@data.update(
|
176
|
+
hash
|
177
|
+
)
|
74
178
|
end
|
75
179
|
|
76
|
-
#
|
180
|
+
# ----------------------------------------------------------------------
|
181
|
+
|
182
|
+
def update(hash)
|
183
|
+
@data.update(
|
184
|
+
hash.stringify
|
185
|
+
)
|
186
|
+
end
|
187
|
+
|
188
|
+
# ----------------------------------------------------------------------
|
189
|
+
|
190
|
+
def to_enum
|
191
|
+
@data.each_with_object({}) do |(k, v), h|
|
192
|
+
if v.is_a?(Hash)
|
193
|
+
then v = self.class.new(v, {
|
194
|
+
:root => root_data
|
195
|
+
})
|
196
|
+
end
|
197
|
+
|
198
|
+
h[k] = v
|
199
|
+
end.to_enum
|
200
|
+
end
|
201
|
+
|
202
|
+
# ----------------------------------------------------------------------
|
203
|
+
# Merge a hash into the metadata. If you merge non-queryable data
|
204
|
+
# it will then get merged into the queryable data.
|
205
|
+
# ----------------------------------------------------------------------
|
77
206
|
|
78
207
|
def merge(new_)
|
79
|
-
|
208
|
+
if !queryable?(:query_data => new_) && queryable?
|
209
|
+
new_ = {
|
210
|
+
:all => new_
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
new_ = new_.stringify
|
215
|
+
self.class.new(@data.deep_merge(new_), {
|
216
|
+
:root => root_data
|
217
|
+
})
|
218
|
+
end
|
219
|
+
|
220
|
+
# ----------------------------------------------------------------------
|
221
|
+
# Destructive merging (@see self#merge)
|
222
|
+
# ----------------------------------------------------------------------
|
223
|
+
|
224
|
+
def merge!(new_)
|
225
|
+
if !queryable?(:query_data => new_) && queryable?
|
226
|
+
new_ = {
|
227
|
+
:all => new_
|
228
|
+
}
|
229
|
+
end
|
230
|
+
|
231
|
+
@data = @data.deep_merge(
|
232
|
+
new_.stringify
|
233
|
+
)
|
234
|
+
|
80
235
|
self
|
81
236
|
end
|
82
237
|
|
83
|
-
#
|
238
|
+
# --------------------------------------------------------------------
|
239
|
+
# Check if a hash is queryable. AKA has "all", "group", "tag".
|
240
|
+
# --------------------------------------------------------------------
|
84
241
|
|
85
|
-
def
|
86
|
-
|
242
|
+
def queryable?(query_data: @data)
|
243
|
+
if query_data.is_a?(self.class)
|
244
|
+
then query_data \
|
245
|
+
.queryable?
|
246
|
+
|
247
|
+
elsif !query_data || !query_data.is_a?(Hash) || query_data.empty?
|
248
|
+
return false
|
249
|
+
|
250
|
+
else
|
251
|
+
(query_data.keys - %w(
|
252
|
+
group tag all
|
253
|
+
)).empty?
|
254
|
+
end
|
87
255
|
end
|
88
256
|
|
89
|
-
#
|
257
|
+
# --------------------------------------------------------------------
|
258
|
+
# Fallback, determining which route is the best. Tag > Group > All.
|
259
|
+
# --------------------------------------------------------------------
|
260
|
+
|
261
|
+
def fallback(group: current_group, tag: current_tag, query_data: @data)
|
262
|
+
if query_data.is_a?(self.class)
|
263
|
+
then query_data.fallback({
|
264
|
+
:group => group, :tag => tag
|
265
|
+
})
|
266
|
+
|
267
|
+
elsif !query_data || !query_data.is_a?(Hash) || query_data.empty?
|
268
|
+
return nil
|
269
|
+
|
270
|
+
else
|
271
|
+
by_tag(:tag => tag, :query_data => query_data) || \
|
272
|
+
by_parent_tag(:tag => tag, :query_data => query_data) || \
|
273
|
+
by_group(:group => group, :query_data => query_data) || \
|
274
|
+
by_parent_group(:tag => tag, :query_data => query_data) || \
|
275
|
+
for_all(:query_data => query_data)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# --------------------------------------------------------------------
|
90
280
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
281
|
+
def for_all(query_data: @data)
|
282
|
+
if query_data.is_a?(self.class)
|
283
|
+
then query_data \
|
284
|
+
.for_all
|
285
|
+
|
286
|
+
elsif !query_data || !query_data.is_a?(Hash)
|
287
|
+
return nil
|
288
|
+
|
289
|
+
else
|
290
|
+
query_data.fetch(
|
291
|
+
"all", nil
|
292
|
+
)
|
293
|
+
end
|
96
294
|
end
|
97
295
|
|
98
|
-
#
|
296
|
+
# --------------------------------------------------------------------
|
99
297
|
|
100
|
-
def
|
101
|
-
|
102
|
-
.
|
103
|
-
|
104
|
-
|
298
|
+
def by_tag(tag: current_tag, query_data: @data)
|
299
|
+
if query_data.is_a?(self.class)
|
300
|
+
then query_data.by_tag({
|
301
|
+
:tag => tag
|
302
|
+
})
|
303
|
+
|
304
|
+
elsif !query_data || !query_data.is_a?(Hash)
|
305
|
+
return nil
|
306
|
+
|
307
|
+
else
|
308
|
+
query_data.fetch("tag", {}).fetch(
|
309
|
+
tag, nil
|
310
|
+
)
|
311
|
+
end
|
105
312
|
end
|
106
313
|
|
107
|
-
#
|
314
|
+
# ----------------------------------------------------------------------
|
315
|
+
|
316
|
+
def by_parent_tag(tag: current_tag, query_data: @data)
|
317
|
+
if aliased_tag == current_tag || !complex_alias?
|
318
|
+
return nil
|
108
319
|
|
109
|
-
|
110
|
-
|
111
|
-
|
320
|
+
else
|
321
|
+
by_tag({
|
322
|
+
:query_data => query_data,
|
323
|
+
:tag => aliased_tag({
|
324
|
+
:tag => tag
|
325
|
+
})
|
326
|
+
})
|
327
|
+
end
|
112
328
|
end
|
113
329
|
|
114
|
-
#
|
330
|
+
# --------------------------------------------------------------------
|
115
331
|
|
116
|
-
def
|
117
|
-
|
332
|
+
def by_group(group: current_group, query_data: @data)
|
333
|
+
if query_data.is_a?(self.class)
|
334
|
+
then query_data.by_group({
|
335
|
+
:group => group
|
336
|
+
})
|
337
|
+
|
338
|
+
elsif !query_data || !query_data.is_a?(Hash)
|
339
|
+
return nil
|
340
|
+
|
341
|
+
else
|
342
|
+
query_data.fetch("group", {}).fetch(
|
343
|
+
group, nil
|
344
|
+
)
|
345
|
+
end
|
118
346
|
end
|
119
347
|
|
120
|
-
#
|
121
|
-
# "tag" key with the given tags. ("tags" is a `Hash`)
|
348
|
+
# ----------------------------------------------------------------------
|
122
349
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
350
|
+
def by_parent_group(tag: current_tag, query_data: @data)
|
351
|
+
if aliased_tag == current_tag || !complex_alias?
|
352
|
+
return nil
|
353
|
+
|
354
|
+
else
|
355
|
+
by_group({
|
356
|
+
:query_data => query_data,
|
357
|
+
:group => aliased_group({
|
358
|
+
:tag => tag
|
359
|
+
})
|
360
|
+
})
|
361
|
+
end
|
128
362
|
end
|
129
363
|
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
364
|
+
# ----------------------------------------------------------------------
|
365
|
+
# Checks to see if the current metadata is an alias of another. This
|
366
|
+
# happens when the user has the tag in aliases but it's not complex.
|
367
|
+
# ----------------------------------------------------------------------
|
133
368
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
137
|
-
return unless key?("type")
|
138
|
-
return unless type
|
369
|
+
def alias?
|
370
|
+
!!(aliased_tag && aliased_tag != tag)
|
371
|
+
end
|
139
372
|
|
140
|
-
|
141
|
-
|
373
|
+
# ----------------------------------------------------------------------
|
374
|
+
# A complex alias happens when the user has an alias but also tries to
|
375
|
+
# add extra data, this allows them to use data from all parties. This
|
376
|
+
# allows them to reap the benefits of having shared data but sometimes
|
377
|
+
# independent data that diverges into it's own.
|
378
|
+
# ----------------------------------------------------------------------
|
379
|
+
|
380
|
+
def complex_alias?
|
381
|
+
if !alias?
|
382
|
+
return false
|
383
|
+
|
384
|
+
else
|
385
|
+
!!root_data.find do |_, v|
|
386
|
+
(v.is_a?(self.class) || v.is_a?(Hash)) && queryable?(:query_data => v) \
|
387
|
+
&& by_tag(:query_data => v)
|
388
|
+
end
|
389
|
+
end
|
142
390
|
end
|
143
391
|
|
144
|
-
#
|
392
|
+
# ----------------------------------------------------------------------
|
145
393
|
|
146
|
-
|
147
|
-
|
148
|
-
if
|
149
|
-
|
394
|
+
def aliased_tag(tag: current_tag)
|
395
|
+
aliases = root_data[:aliases]
|
396
|
+
if aliases.nil? || !aliases.key?(tag)
|
397
|
+
tag
|
398
|
+
|
399
|
+
else
|
400
|
+
aliases[
|
401
|
+
tag
|
402
|
+
]
|
150
403
|
end
|
151
|
-
key
|
152
404
|
end
|
153
405
|
|
154
|
-
#
|
406
|
+
# ----------------------------------------------------------------------
|
407
|
+
|
408
|
+
def aliased_group(tag: current_tag)
|
409
|
+
root_data[:tags][aliased_tag({
|
410
|
+
:tag => tag
|
411
|
+
})]
|
412
|
+
end
|
413
|
+
|
414
|
+
# ----------------------------------------------------------------------
|
415
|
+
# Converts the current meta into a string.
|
416
|
+
# ----------------------------------------------------------------------
|
417
|
+
|
418
|
+
def to_s(raw: false, shell: false)
|
419
|
+
if !raw && (mergeable_hash? || mergeable_array?)
|
420
|
+
to_a(:shell => shell).join(" #{
|
421
|
+
"\n" if shell
|
422
|
+
}")
|
423
|
+
|
424
|
+
elsif !raw && queryable?
|
425
|
+
then fallback \
|
426
|
+
.to_s
|
427
|
+
|
428
|
+
else
|
429
|
+
@data.to_s
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# ----------------------------------------------------------------------
|
434
|
+
|
435
|
+
def to_a(raw: false, shell: false)
|
436
|
+
if raw
|
437
|
+
return to_h({
|
438
|
+
:raw => true
|
439
|
+
}).to_a
|
440
|
+
|
441
|
+
elsif !mergeable_array?
|
442
|
+
to_h.each_with_object([]) do |(k, v), a|
|
443
|
+
a << "#{k}=#{
|
444
|
+
shell ? v.to_s.shellescape : v
|
445
|
+
}"
|
446
|
+
end
|
447
|
+
else
|
448
|
+
(for_all || []) | (by_parent_group || []) | (by_group || []) | \
|
449
|
+
(by_parent_tag || []) | (by_tag || [])
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# ----------------------------------------------------------------------
|
454
|
+
# Convert a `Metadata' into a normal hash. If `self' is queryable then
|
455
|
+
# we go and start merging values smartly. This means that we will merge
|
456
|
+
# all the arrays into one another and we will merge hashes into hashes.
|
457
|
+
# ----------------------------------------------------------------------
|
458
|
+
|
459
|
+
def to_h(raw: false)
|
460
|
+
return @data.to_h if raw || !queryable? || !mergeable_hash?
|
461
|
+
keys = [for_all, by_group, by_parent_group, by_tag, \
|
462
|
+
by_parent_tag].compact.map(&:keys)
|
463
|
+
|
464
|
+
keys.reduce(:+).each_with_object({}) do |k, h|
|
465
|
+
vals = [for_all, by_group, by_parent_group, by_tag, \
|
466
|
+
by_parent_tag].compact
|
467
|
+
|
468
|
+
h[k] = \
|
469
|
+
if mergeable_array?(k)
|
470
|
+
vals.map { |v| v[k].to_a } \
|
471
|
+
.compact.reduce(
|
472
|
+
:+
|
473
|
+
)
|
474
|
+
|
475
|
+
elsif mergeable_hash?(k)
|
476
|
+
vals.map { |v| v[k].to_h } \
|
477
|
+
.compact.reduce(
|
478
|
+
:deep_merge
|
479
|
+
)
|
480
|
+
|
481
|
+
else
|
482
|
+
vals.find do |v|
|
483
|
+
v[k]
|
484
|
+
end \
|
485
|
+
[k]
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# ----------------------------------------------------------------------
|
491
|
+
|
492
|
+
def mergeable_hash?(key = nil)
|
493
|
+
return false unless queryable?
|
494
|
+
vals = [by_parent_tag, by_parent_group, \
|
495
|
+
by_tag, for_all, by_group].compact
|
496
|
+
|
497
|
+
if key
|
498
|
+
vals = vals.map do |val|
|
499
|
+
val[key]
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
!vals.empty? && !vals.any? do |val|
|
504
|
+
!val.is_a?(Hash) && !val.is_a?(
|
505
|
+
self.class
|
506
|
+
)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
# ----------------------------------------------------------------------
|
511
|
+
|
512
|
+
def mergeable_array?(key = nil)
|
513
|
+
return false unless queryable?
|
514
|
+
vals = [by_parent_tag, by_parent_group, \
|
515
|
+
by_tag, for_all, by_group].compact
|
516
|
+
|
517
|
+
if key
|
518
|
+
vals = vals.map do |val|
|
519
|
+
val[key]
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
!vals.empty? && !vals.any? do |val|
|
524
|
+
!val.is_a?(
|
525
|
+
Array
|
526
|
+
)
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
# ----------------------------------------------------------------------
|
531
|
+
|
532
|
+
def current_group
|
533
|
+
root_data[:tags][current_tag] ||
|
534
|
+
"normal"
|
535
|
+
end
|
536
|
+
|
537
|
+
# ----------------------------------------------------------------------
|
538
|
+
# HELPER: Get a list of all the tags.
|
539
|
+
# ----------------------------------------------------------------------
|
540
|
+
|
541
|
+
def tags
|
542
|
+
(root_data[:tags] || {}).keys | (root_data[:aliases] || {}).keys
|
543
|
+
end
|
544
|
+
|
545
|
+
# ----------------------------------------------------------------------
|
546
|
+
# HELPER: Get a list of all the groups.
|
547
|
+
# ----------------------------------------------------------------------
|
548
|
+
|
549
|
+
def groups
|
550
|
+
root_data["tags"].values.uniq
|
551
|
+
end
|
552
|
+
|
553
|
+
# ----------------------------------------------------------------------
|
155
554
|
|
156
555
|
private
|
157
|
-
def
|
158
|
-
|
159
|
-
return
|
160
|
-
val
|
556
|
+
def merge_or_override(val, new_val)
|
557
|
+
return new_val unless val
|
558
|
+
return val if val.is_a?(String) && !new_val || !new_val.is_a?(val.class)
|
559
|
+
return new_val.merge(val) if val.respond_to?(:merge)
|
560
|
+
return new_val | val if val.respond_to?(:|)
|
561
|
+
end
|
562
|
+
|
563
|
+
# ----------------------------------------------------------------------
|
564
|
+
|
565
|
+
private
|
566
|
+
def string_wrapper(obj, shell: false)
|
567
|
+
return obj if obj == true || obj == false || obj.nil?
|
568
|
+
return obj.to_s(:shell => shell) if obj.is_a?(self.class)
|
569
|
+
!obj.is_a?(Array) ? obj.to_s : obj.join(
|
570
|
+
"\s"
|
571
|
+
)
|
161
572
|
end
|
573
|
+
|
574
|
+
# ----------------------------------------------------------------------
|
575
|
+
|
576
|
+
private
|
577
|
+
def method_missing(method, *args, shell: false, &block)
|
578
|
+
key = method.to_s.gsub(/\?$/, "")
|
579
|
+
val = self[key] || self[key.singularize] \
|
580
|
+
|| self[key.pluralize]
|
581
|
+
|
582
|
+
if !args.empty? || block_given?
|
583
|
+
super
|
584
|
+
|
585
|
+
elsif method !~ /\?$/
|
586
|
+
string_wrapper(
|
587
|
+
val, :shell => shell
|
588
|
+
)
|
589
|
+
|
590
|
+
else
|
591
|
+
val != false && !val.nil? && \
|
592
|
+
!val.empty?
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# ----------------------------------------------------------------------
|
597
|
+
|
598
|
+
alias deep_merge merge
|
599
|
+
alias group current_group
|
600
|
+
rb_delegate :for_all, :to => :self, :type => :hash, :key => :all
|
601
|
+
rb_delegate :current_tag, :to => :root_data, :key => :tag, :type => :hash
|
602
|
+
rb_delegate :tag, :to => :root_data, :type => :hash, :key => :tag
|
603
|
+
rb_delegate :root, :to => :@root, :type => :ivar, :bool => true
|
604
|
+
|
605
|
+
# ----------------------------------------------------------------------
|
606
|
+
|
607
|
+
rb_delegate :fetch, :to => :@data
|
608
|
+
rb_delegate :delete, :to => :@data
|
609
|
+
rb_delegate :empty?, :to => :@data
|
610
|
+
rb_delegate :inspect, :to => :@data
|
611
|
+
rb_delegate :values_at, :to => :@data
|
612
|
+
rb_delegate :values, :to => :@data
|
613
|
+
rb_delegate :keys, :to => :@data
|
614
|
+
rb_delegate :key?, :to => :@data
|
615
|
+
rb_delegate :==, :to => :@data
|
616
|
+
|
617
|
+
# ----------------------------------------------------------------------
|
618
|
+
|
619
|
+
rb_delegate :inject, :to => :to_enum
|
620
|
+
rb_delegate :select, :to => :to_enum
|
621
|
+
rb_delegate :each_with_object, :to => :to_enum
|
622
|
+
rb_delegate :collect, :to => :to_enum
|
623
|
+
rb_delegate :find, :to => :to_enum
|
624
|
+
rb_delegate :each, :to => :to_enum
|
162
625
|
end
|
163
626
|
end
|
164
627
|
end
|