cartage 1.2 → 2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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:
@@ -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