cartage 1.2 → 2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Cartage.yml.rdoc +51 -215
- data/Contributing.md +71 -0
- data/Extending.md +520 -0
- data/History.md +106 -0
- data/{Licence.rdoc → Licence.md} +5 -5
- data/Manifest.txt +19 -12
- data/README.rdoc +64 -109
- data/Rakefile +37 -32
- data/bin/cartage +2 -2
- data/cartage-cli.md +137 -0
- data/cartage.yml.sample +43 -153
- data/lib/cartage.rb +296 -366
- data/lib/cartage/backport.rb +66 -0
- data/lib/cartage/cli.rb +234 -0
- data/lib/cartage/commands/echo.rb +23 -0
- data/lib/cartage/commands/info.rb +57 -0
- data/lib/cartage/commands/manifest.rb +88 -0
- data/lib/cartage/commands/pack.rb +18 -0
- data/lib/cartage/config.rb +118 -125
- data/lib/cartage/core.rb +112 -0
- data/lib/cartage/gli_ext.rb +58 -0
- data/lib/cartage/minitest.rb +140 -0
- data/lib/cartage/plugin.rb +137 -25
- data/lib/cartage/plugins/build_tarball.rb +40 -0
- data/lib/cartage/{manifest.rb → plugins/manifest.rb} +47 -35
- data/test/minitest_config.rb +3 -65
- data/test/test_cartage.rb +253 -109
- data/test/test_cartage_build_tarball.rb +57 -0
- data/test/test_cartage_config.rb +100 -28
- data/test/test_cartage_core.rb +172 -0
- data/test/test_cartage_manifest.rb +234 -13
- data/test/test_cartage_plugin.rb +85 -0
- metadata +63 -79
- data/.autotest +0 -8
- data/.gemtest +0 -1
- data/.minitest.rb +0 -2
- data/.travis.yml +0 -36
- data/Contributing.rdoc +0 -66
- data/Gemfile +0 -9
- data/History.rdoc +0 -43
- data/lib/cartage/command.rb +0 -59
- data/lib/cartage/manifest/commands.rb +0 -106
- data/lib/cartage/pack_command.rb +0 -14
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :nocov:
|
4
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
|
5
|
+
# An implementation of #dig for Ruby pre-2.3. Based originally on
|
6
|
+
# {Invoca/ruby_dig}[https://github.com/Invoca/ruby_dig] with some inspiration
|
7
|
+
# from {jrochkind/dig_rb}[https://github.com/jrochkind/dig_rb].
|
8
|
+
module Cartage::Dig #:nodoc:
|
9
|
+
def dig(key, *rest)
|
10
|
+
value = self[key]
|
11
|
+
|
12
|
+
if value.nil? || rest.empty?
|
13
|
+
value
|
14
|
+
elsif value.respond_to?(:dig)
|
15
|
+
value.dig(*rest)
|
16
|
+
else
|
17
|
+
fail TypeError, "#{value.class} does not have #dig method"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
::Array.send(:include, Cartage::Dig) unless ::Array.public_method_defined?(:dig)
|
23
|
+
::Hash.send(:include, Cartage::Dig) unless ::Hash.public_method_defined?(:dig)
|
24
|
+
|
25
|
+
unless ::Struct.public_method_defined?(:dig)
|
26
|
+
# Struct gets a different override.
|
27
|
+
module Cartage::StructDig # :nodoc:
|
28
|
+
include Cartage::Dig
|
29
|
+
|
30
|
+
# This override is necessary because <tt>Struct.new(:a).new(1)[0]</tt> is
|
31
|
+
# *legal*. So we don't just care about NameError, but IndexError as well.
|
32
|
+
def dig(name, *rest)
|
33
|
+
super
|
34
|
+
rescue IndexError, NameError
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
::Struct.send(:include, Cartage::StructDig)
|
40
|
+
end
|
41
|
+
|
42
|
+
unless ::OpenStruct.public_method_defined?(:dig)
|
43
|
+
# OpenStruct gets a different override.
|
44
|
+
module Cartage::OpenStructDig # :nodoc:
|
45
|
+
def dig(name, *rest)
|
46
|
+
@table.dig(name.to_sym, *rest)
|
47
|
+
rescue NoMethodError
|
48
|
+
raise TypeError, "#{name} is not a symbol nor a string"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
::OpenStruct.send(:include, Cartage::OpenStructDig)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
unless Pathname.public_method_defined?(:write)
|
57
|
+
##
|
58
|
+
module Cartage::PathnameWrite #:nodoc:
|
59
|
+
def write(*args)
|
60
|
+
IO.write(to_s, *args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
::Pathname.send(:include, Cartage::PathnameWrite)
|
65
|
+
end
|
66
|
+
# :nocov:
|
data/lib/cartage/cli.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cartage'
|
4
|
+
require_relative 'gli_ext'
|
5
|
+
|
6
|
+
::GLI::Commands::Help.skips_pre = false
|
7
|
+
|
8
|
+
##
|
9
|
+
class Cartage
|
10
|
+
# The Cartage command-line application, as a GLI application.
|
11
|
+
class CLI
|
12
|
+
# Commands that want to return a non-zero exit code without a displayed
|
13
|
+
# message should raise Cartage::QuietExit with the exit status.
|
14
|
+
class QuietExit < StandardError
|
15
|
+
include ::GLI::StandardException
|
16
|
+
|
17
|
+
def initialize(exit_code) #:nodoc:
|
18
|
+
@exit_code = exit_code
|
19
|
+
end
|
20
|
+
|
21
|
+
# The exit code to be used.
|
22
|
+
attr_reader :exit_code
|
23
|
+
end
|
24
|
+
|
25
|
+
# A local alias for GLI::CustomExit. Initialize with +message+ and
|
26
|
+
# +exit_status+. The message will be displayed, and the exit status will be
|
27
|
+
# returned. This should be used for *failure* of the command.
|
28
|
+
class CustomExit < ::GLI::CustomExit; end
|
29
|
+
|
30
|
+
# A local alias for GLI::CommandException. This exception is similar to
|
31
|
+
# CustomExit, but should be used when there is a configuration issue, or a
|
32
|
+
# conflict in flags or switches. It requires three parameters:
|
33
|
+
#
|
34
|
+
# 1. The message;
|
35
|
+
# 2. The command name for help (+command.name_for_help+);
|
36
|
+
# 3. The exit status.
|
37
|
+
class CommandException < ::GLI::CommandException; end
|
38
|
+
|
39
|
+
Cartage::Plugin.load
|
40
|
+
|
41
|
+
include ::GLI::App #:nodoc:
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# When called with a block, this method reopens Cartage::CLI to add new
|
45
|
+
# commands. If modules are provided, the normal Object#extend method
|
46
|
+
# will be called.
|
47
|
+
#
|
48
|
+
# +mods+ are the modules to extend onto Cartage::CLI. +block+ is the
|
49
|
+
# block that contains CLI command extensions (using the GLI DSL).
|
50
|
+
def extend(*mods, &block)
|
51
|
+
super(*mods) unless mods.empty?
|
52
|
+
cli.instance_eval(&block) if block_given?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Run the application. Should only be run from +bin/cartage+. +args+ are
|
56
|
+
# the command-line arguments (e.g., +ARGV+) to the CLI.
|
57
|
+
def run(*args)
|
58
|
+
cli.run(*args)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def cli
|
64
|
+
@cli ||= new
|
65
|
+
end
|
66
|
+
|
67
|
+
private :new
|
68
|
+
end
|
69
|
+
|
70
|
+
# The Cartage configuration and execution engine for the running instance
|
71
|
+
# of the Cartage CLI. The first time that this is used, an explicit
|
72
|
+
# +config+ (which must be a Cartage::Config object) may be provided.
|
73
|
+
def cartage(config = nil)
|
74
|
+
if defined?(@cartage) && @cartage && config
|
75
|
+
fail 'Cannot provide another configuration after initialization.'
|
76
|
+
end
|
77
|
+
@cartage ||= Cartage.new(config)
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_writer :backtrace # :nodoc:
|
81
|
+
|
82
|
+
# Indicates whether backtrace printing is turned on.
|
83
|
+
def backtrace?
|
84
|
+
!!@backtrace
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
CLI.extend do
|
89
|
+
accept(Cartage::Config) do |value|
|
90
|
+
Cartage::Config.load(value)
|
91
|
+
end
|
92
|
+
|
93
|
+
program_desc 'Manage releaseable packages'
|
94
|
+
program_long_desc <<-'DESC'
|
95
|
+
Cartage provides a repeatable means to create a package for a server-side
|
96
|
+
application that can be used in deployment with a configuration tool like
|
97
|
+
Ansible, Chef, Puppet, or Salt.
|
98
|
+
DESC
|
99
|
+
version Cartage::VERSION
|
100
|
+
|
101
|
+
subcommand_option_handling :normal
|
102
|
+
arguments :strict
|
103
|
+
synopsis_format :terminal
|
104
|
+
|
105
|
+
desc 'Silence normal output.'
|
106
|
+
switch %i(q quiet)
|
107
|
+
|
108
|
+
desc 'Show verbose output.'
|
109
|
+
switch %i(v verbose)
|
110
|
+
|
111
|
+
desc 'Show the backtrace when an error occurs.'
|
112
|
+
switch %i(T trace), negatable: false
|
113
|
+
|
114
|
+
desc 'Use the specified Cartage configuration file.'
|
115
|
+
long_desc <<-'DESC'
|
116
|
+
Use the specified configuration file. If not specified, the configuration is
|
117
|
+
found relative to the current working directory (assumed to be the project
|
118
|
+
root) at one of the following locations:
|
119
|
+
|
120
|
+
1. config/cartage.yml
|
121
|
+
2. cartage.yml
|
122
|
+
3. .cartage.yml
|
123
|
+
DESC
|
124
|
+
flag [ :C, 'config-file' ],
|
125
|
+
type: Cartage::Config,
|
126
|
+
arg_name: :FILE,
|
127
|
+
default_value: :'<project-config>'
|
128
|
+
|
129
|
+
desc 'The name of the package.'
|
130
|
+
long_desc <<-'DESC'
|
131
|
+
The name of the package is used with the timestamp to create the final package
|
132
|
+
name. Defaults to the last part of the repo URL.
|
133
|
+
DESC
|
134
|
+
flag %i(n name), arg_name: :NAME, default_value: :'<repo-name>'
|
135
|
+
|
136
|
+
desc 'The destination of the created package.'
|
137
|
+
long_desc 'Where the final package will be written.'
|
138
|
+
flag %i(t target), arg_name: :PATH, default_value: :tmp
|
139
|
+
|
140
|
+
desc 'The package source root path.'
|
141
|
+
long_desc <<-'DESC'
|
142
|
+
Where the package is built from. Defaults to the root of the repository.
|
143
|
+
DESC
|
144
|
+
flag [ :r, 'root-path' ], arg_name: :PATH, default_value: :'<repo-root-path>'
|
145
|
+
|
146
|
+
desc 'The timestamp used for building the package.'
|
147
|
+
long_desc <<-'DESC'
|
148
|
+
The timestamp is used with the name of the package is used to create the final
|
149
|
+
package name.
|
150
|
+
DESC
|
151
|
+
flag [ :timestamp ], arg_name: :TIMESTAMP
|
152
|
+
|
153
|
+
desc 'Disable the use of vendor dependency caching.'
|
154
|
+
switch [ 'disable-dependency-cache' ], negatable: false
|
155
|
+
|
156
|
+
desc 'The path where vendored dependencies will be cached between builds.'
|
157
|
+
long_desc <<-'DESC'
|
158
|
+
Dependencies for deployable packages are vendored. To reduce network calls and
|
159
|
+
build time, Cartage will cache the vendored dependencies in a tarball
|
160
|
+
(dependency-cache.tar.bz2) in this path.
|
161
|
+
DESC
|
162
|
+
flag [ 'dependency-cache-path' ],
|
163
|
+
arg_name: :PATH,
|
164
|
+
default_value: :'<default-dependency-cache-path>'
|
165
|
+
|
166
|
+
commands_from 'cartage/commands'
|
167
|
+
|
168
|
+
desc 'Save the computed configuration as a configuration file.'
|
169
|
+
arg :'CONFIG-FILE'
|
170
|
+
command '_save' do |save|
|
171
|
+
save.hide!
|
172
|
+
|
173
|
+
save.action do |_global, _options, args|
|
174
|
+
name = args.first
|
175
|
+
name = "#{name}.yml" unless name == '-' || name.end_with?('.yml')
|
176
|
+
|
177
|
+
Cartage::Config.new(cartage.config).tap do |config|
|
178
|
+
config.name ||= cartage.name
|
179
|
+
config.root_path ||= cartage.root_path.to_s
|
180
|
+
config.target ||= cartage.target.to_s
|
181
|
+
config.timestamp ||= cartage.timestamp.to_s
|
182
|
+
config.compression ||= cartage.compression.to_s
|
183
|
+
config.disable_dependency_cache = cartage.disable_dependency_cache
|
184
|
+
config.dependency_cache_path = cartage.dependency_cache_path.to_s
|
185
|
+
config.quiet = cartage.quiet || false
|
186
|
+
config.verbose = cartage.verbose || false
|
187
|
+
|
188
|
+
if name == '-'
|
189
|
+
puts config.to_yaml
|
190
|
+
else
|
191
|
+
Pathname(name).write(config.to_yaml)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
on_error do |ex|
|
198
|
+
unless ex.kind_of?(Cartage::CLI::QuietExit)
|
199
|
+
output_error_message(ex)
|
200
|
+
ex.backtrace.each { |l| puts l } if backtrace?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
pre do |global, _command, _options, _args|
|
205
|
+
self.backtrace = global[:trace]
|
206
|
+
|
207
|
+
clear_defaults_from(global)
|
208
|
+
|
209
|
+
config = case global['config-file']
|
210
|
+
when Cartage::Config
|
211
|
+
global['config-file']
|
212
|
+
when nil
|
213
|
+
Cartage::Config.load(:default)
|
214
|
+
else
|
215
|
+
fail 'Invalid config-file'
|
216
|
+
end
|
217
|
+
|
218
|
+
# Configure Cartage from the config file and global switches.
|
219
|
+
config.disable_dependency_cache ||= global['disable-dependency-cache']
|
220
|
+
config.quiet ||= global['quiet']
|
221
|
+
config.verbose ||= global['verbose']
|
222
|
+
|
223
|
+
config.name = global['name'] if global['name']
|
224
|
+
config.target = global['target'] if global['target']
|
225
|
+
config.root_path = global['root-path'] if global['root-path']
|
226
|
+
config.timestamp = global['timestamp'] if global['timestamp']
|
227
|
+
if global['dependency-cache-path']
|
228
|
+
config.dependency_cache_path = global['dependency-cache-path']
|
229
|
+
end
|
230
|
+
|
231
|
+
cartage(config)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Call +extend+ Cartage::CLI to add the commands defined in the block.
|
5
|
+
Cartage::CLI.extend do
|
6
|
+
desc 'Echo the provided text'
|
7
|
+
arg :TEXT, :multiple
|
8
|
+
command 'echo' do |echo|
|
9
|
+
echo.hide!
|
10
|
+
echo.desc 'Suppress newlines'
|
11
|
+
echo.switch [ :c, 'no-newlines' ], negatable: false
|
12
|
+
echo.action do |_g, options, args|
|
13
|
+
unless cartage.quiet
|
14
|
+
message = args.join(' ')
|
15
|
+
if options['no-newlines'] || cartage.config(for_command: 'echo').no_newlines
|
16
|
+
puts message
|
17
|
+
else
|
18
|
+
print message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Call +extend+ Cartage::CLI to add the commands defined in the block.
|
5
|
+
Cartage::CLI.extend do
|
6
|
+
# Use +desc+ to write a description about the next command.
|
7
|
+
desc 'Show information about Cartage itself'
|
8
|
+
|
9
|
+
# Use +long_desc+ to write a long description about the next command that can
|
10
|
+
# be included in a generated RDoc file.
|
11
|
+
long_desc <<-'DESC'
|
12
|
+
Shows various bits of information about Cartage based on the running instance.
|
13
|
+
Some plug-ins do run-time resolution of configuration values, so what is shown
|
14
|
+
through these subcommands will not represent that resolution.
|
15
|
+
DESC
|
16
|
+
|
17
|
+
# Declare a new command with +command+ and one or more names for the command.
|
18
|
+
# This command is available as both <tt>cartage info</tt> and <tt>cartage
|
19
|
+
# i</tt>. The block contains the definition of the switches, flags,
|
20
|
+
# subcommands, and actions for this command.
|
21
|
+
command %w(info i) do |info|
|
22
|
+
# Use this extension method to hide this command. GLI 2.13 does not support
|
23
|
+
# hiding subcommands.
|
24
|
+
info.hide!
|
25
|
+
|
26
|
+
# The same GLI::DSL works on the command being created, Here, we are
|
27
|
+
# creating <tt>cartage info plugins</tt>.
|
28
|
+
info.desc 'Show the plug-ins that have been found.'
|
29
|
+
info.long_desc <<-'DESC'
|
30
|
+
Only plug-ins using the class plug-in protocol are found this way. Plug-ins
|
31
|
+
that just provide commands are not reported as plugins.
|
32
|
+
DESC
|
33
|
+
info.command 'plugins' do |plugins|
|
34
|
+
# Implement the #action as a block. It accepts three parameters: the
|
35
|
+
# global options hash, the local command options hash, and any arguments
|
36
|
+
# passed on the command-line.
|
37
|
+
#
|
38
|
+
# Within a command's action, a +cartage+ object is available that
|
39
|
+
# represents the Cartage instance that will be used for packaging.
|
40
|
+
plugins.action do |_global, _options, _args|
|
41
|
+
plugs = cartage.plugins.enabled
|
42
|
+
if plugs.empty?
|
43
|
+
puts 'No active plug-ins.'
|
44
|
+
else
|
45
|
+
plugs.map! do |plug|
|
46
|
+
"* #{plug.plugin_name} (#{plug.version})"
|
47
|
+
end
|
48
|
+
puts <<-plugins
|
49
|
+
Active Plug-ins:
|
50
|
+
|
51
|
+
#{plugs.join("\n")}
|
52
|
+
plugins
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Cartage::CLI.extend do
|
4
|
+
desc 'Work with the Manifest.txt file'
|
5
|
+
long_desc <<-'DESC'
|
6
|
+
Commands that manage the Cartage manifest (Manifest.txt) and ignore
|
7
|
+
(.cartignore) files for a project.
|
8
|
+
DESC
|
9
|
+
command 'manifest' do |manifest|
|
10
|
+
manifest.desc 'Check the Manifest.txt file against the current repository'
|
11
|
+
manifest.long_desc <<-'DESC'
|
12
|
+
Compares the current Cartage manifest against what would be calculated. If
|
13
|
+
there is a difference, it will be displayed in unified ('diff -u') format and a
|
14
|
+
non-zero exit status will be returned.
|
15
|
+
|
16
|
+
The diff output can be suppressed by specifying -q on cartage:
|
17
|
+
|
18
|
+
cartage -q manifest check
|
19
|
+
DESC
|
20
|
+
manifest.command 'check' do |check|
|
21
|
+
check.action do |_global, _options, _args|
|
22
|
+
cartage.manifest.check or
|
23
|
+
fail Cartage::CLI::QuietExit, $?.exitstatus
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
manifest.desc 'Install or update the .cartignore file'
|
28
|
+
manifest.long_desc <<-'DESC'
|
29
|
+
Creates, replaces, or updates the Cartage ignore file (.cartignore). Will not
|
30
|
+
modify an existing .cartignore unless the --mode is overwrite or merge.
|
31
|
+
DESC
|
32
|
+
manifest.command 'cartignore' do |cartignore|
|
33
|
+
cartignore.desc 'Overwrite or merge an existing .cartignore'
|
34
|
+
cartignore.long_desc <<-'DESC'
|
35
|
+
Sets the mode for updating the ignore file. When --mode overwrite, the ignore
|
36
|
+
file will be replaced with the default ignore file contents. When --mode merge,
|
37
|
+
the existing ignore file will be merged with the default ignore file contents.
|
38
|
+
DESC
|
39
|
+
cartignore.flag :mode, must_match: %w(overwrite merge), arg_name: :MODE
|
40
|
+
|
41
|
+
cartignore.desc 'Overwrite an existing .cartignore (--mode overwrite)'
|
42
|
+
cartignore.switch %i(f force overwrite), negatable: false
|
43
|
+
|
44
|
+
cartignore.desc 'Merge an existing .cartignore (--mode merge)'
|
45
|
+
cartignore.switch %i(m merge), negatable: false
|
46
|
+
|
47
|
+
cartignore.action do |_global, options, _args|
|
48
|
+
message = if options['merge'] && options['force']
|
49
|
+
'Cannot mix options --force and --merge'
|
50
|
+
elsif options['merge'] && options['mode'] == 'overwrite'
|
51
|
+
'Cannot mix option --merge and --mode overwrite'
|
52
|
+
elsif options['force'] && options['mode'] == 'merge'
|
53
|
+
'Cannot mix option --force and --mode merge'
|
54
|
+
end
|
55
|
+
|
56
|
+
if message
|
57
|
+
fail Cartage::CLI::CommandException.new(message, cartignore.name_for_help, 64)
|
58
|
+
end
|
59
|
+
|
60
|
+
mode = if options['merge'] || options['mode'] == 'merge'
|
61
|
+
'merge'
|
62
|
+
elsif options['force'] || options['mode'] == 'overwrite'
|
63
|
+
'overwrite'
|
64
|
+
end
|
65
|
+
|
66
|
+
cartage.manifest.install_default_ignore(mode: mode)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
manifest.desc 'Show the files that will be included in the package'
|
71
|
+
manifest.command 'show' do |show|
|
72
|
+
show.action do |_global, _options, _args|
|
73
|
+
cartage.manifest.resolve do |tmpfile|
|
74
|
+
puts Pathname(tmpfile).read
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
manifest.desc 'Generate or update the Manifest.txt file'
|
80
|
+
manifest.command %w(generate update) do |generate|
|
81
|
+
generate.action do |_global, _options, _args|
|
82
|
+
cartage.manifest.generate
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
manifest.default_command :check
|
87
|
+
end
|
88
|
+
end
|