cartage 1.2 → 2.0.rc1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93cd6f86971a0f549be3d3926c0af79468217630
4
- data.tar.gz: acff341e0fccbdf7d6a96aab9faffbefcedb19a1
3
+ metadata.gz: c43e24d4e0f7258cdbd39dcc488e76243f735167
4
+ data.tar.gz: f8cbe8898d7a8a7332b41415696c1cc206ddabe9
5
5
  SHA512:
6
- metadata.gz: b49e371c9d7c42af2a8030f3356aeaa2063e5d4cc2835dbc43886bc076988d8cb09f6139568bfdcc57784bb06655d59ee17dde75bdda4a240d09130456cab4c9
7
- data.tar.gz: ecf1af90ef466bd692e0b671b270fa0b5cec37988bb7a5caf445126ebcb88896b339cd80cbbc4d8610fd75036fc521a1f81bd24c747d7a8a50b16a8cecd2e209
6
+ metadata.gz: cc9bdac5b457d1954b97a7ef92b32a41e5b60c12993ab7b9d154723870991aa671b2f8dcda9ef1c70e708c4931ac65dc17d38299464a6f6189748f247dd19ecb
7
+ data.tar.gz: 1bc2c43ded9eed5e7e18b66c589e2879fa7cb6989c1a714bc9ba9ba2c6f258fb17d167be653b1c0be3650bd8f4cf7e7e81afef9e95ff2c75a84b9da73fe4a86c
@@ -11,38 +11,37 @@ configuration file. The main value that would probably be set under most
11
11
  circumstances is the +name+ value.
12
12
 
13
13
  Because Cartage reads its configuration through ERB, the following structure is
14
- recommended for +config/cartage.yml+.
14
+ recommended for +config/cartage.yml+. Note that Cartage::Config.import is
15
+ shorthand for <tt>File.read(filename) if File.exist?(filename)</tt>. Both
16
+ +config/ansible/cartage.yml+ and +config/local/cartage.yml+ should be in your
17
+ +.gitignore+.
15
18
 
16
19
  ---
17
20
  name: my-application
18
21
 
19
22
  # This must not be indented for it to work.
20
- <% if File.exist?('config/ansible/cartage.yml') %>
21
- <%= File.read('config/ansible/cartage.yml') %>
22
- <% end %>
23
- <% if File.exist?('config/local/cartage.yml') %>
24
- <%= File.read('config/local/cartage.yml') %>
25
- <% end %>
23
+ <%= Cartage::Config.import 'config/ansible/cartage.yml' %>
24
+ <%= Cartage::Config.import 'config/local/cartage.yml' %>
26
25
 
27
26
  == Main Cartage Configuration Options
28
27
 
29
28
  === +name+
30
29
 
31
30
  The name of the application. Optional, defaults to the basename of the origin
32
- Git URL. Overridden with <tt>cartage --name NAME</tt>.
31
+ repo URL. Overridden with <tt>cartage --name NAME</tt>.
33
32
 
34
33
  name: my-application
35
34
 
36
35
  === +target+
37
36
 
38
- The target path for the Cartage package. Optional and defaults to
39
- <tt>./tmp</tt>. Overridden with <tt>cartage --target PATH</tt>.
37
+ The target path for the Cartage package. Optional, defaults to <tt>./tmp</tt>.
38
+ Overridden with <tt>cartage --target PATH</tt>.
40
39
 
41
40
  target: tmp/cartage
42
41
 
43
42
  === +root_path+
44
43
 
45
- The root path of the application. Optional, defaults to the top of the Git
44
+ The root path of the application. Optional, defaults to the top of the
46
45
  repository (<tt>git rev-parse --show-cdup</tt>). Overridden with <tt>cartage
47
46
  --root-path ROOT_PATH</tt>.
48
47
 
@@ -52,232 +51,69 @@ repository (<tt>git rev-parse --show-cdup</tt>). Overridden with <tt>cartage
52
51
 
53
52
  The timestamp for the final package (which is
54
53
  <tt><em>name</em>-<em>timestamp</em></tt>). Optional, defaults to the current
55
- time in UTC. Overridden with <tt>cartage --timestamp TIMESTAMP</tt>. This
56
- value is *not* validated to be a time value when supplied.
54
+ time in UTC. Overridden with <tt>cartage --timestamp TIMESTAMP</tt>. This value
55
+ is *not* validated to be a time value when supplied.
57
56
 
58
57
  timestamp: not-a-timestamp
59
58
 
60
- === +bundle_cache+
59
+ === +compression+
61
60
 
62
- The bundle cache path, where the package’s <tt>vendor-bundle.tar.bz2</tt>
63
- will be stored between builds. Optional, defaults to <tt>./tmp</tt>.
64
- Overridden with <tt>cartage --bundle-cache BUNDLE_CACHE</tt>.
61
+ The type of compression to be used. Optional, defaults to 'bzip2'. Must be one
62
+ of 'bzip2', 'gzip', or 'none'. Overridden with <tt>cartage --compression
63
+ TYPE</tt>.
65
64
 
66
- bundle_cache: tmp/cache
65
+ This affects the compression and filenames of both the final package and the
66
+ dependency cache.
67
67
 
68
- === +without+
68
+ compression: gzip
69
69
 
70
- The groups to exclude from <tt>bundle install</tt>. Optional, defaults to <tt>[
71
- "development", "test"]</tt>. Overridden with <tt>cartage --without
72
- group1,group2</tt>.
70
+ === +quiet+
73
71
 
74
- without:
75
- - development
76
- - test
77
- - other
72
+ Silence normal output. Optional, defaults false. Overridden with <tt>cartage
73
+ --quiet</tt>.
78
74
 
79
- === +plugins+
80
-
81
- A dictionary that contains all plug-in configuration, keyed by plug-in name.
82
-
83
- == Cartage Plug-In Configuration Options
84
-
85
- === cartage-s3
75
+ quiet: true
86
76
 
87
- Cartage-s3 needs to know where to put the completed package and how to log into
88
- the selected storage provider.
77
+ === +verbose+
89
78
 
90
- ==== +path+
79
+ Show verbose output. Optional, defaults false. Overridden with <tt>cartage
80
+ --verbose</tt>.
91
81
 
92
- The path to the cartage S3 bucket or directory (for another service that
93
- Fog::Storage supports). This has no default and is overridden with <tt>cartage
94
- s3 --path PATH</tt>.
82
+ verbose: true
95
83
 
96
- plugins:
97
- s3:
98
- path: cartage-bucket
84
+ === +disable_dependency_cache+
99
85
 
100
- ==== +credentials+
86
+ Disable dependency caching. Optional, defaults false.
101
87
 
102
- The credentials dictionary passed to Fog::Storage for uploads. Each provider
103
- has different keys. If present, this will dictionary be used in preference to
104
- <tt>cartage s3</tt> flags <tt>--key-id</tt>, <tt>--secret-key</tt>, and
105
- <tt>--region</tt> as those work *only* with Amazon AWS S3.
88
+ disable_dependency_cache: true
106
89
 
107
- ===== AWS
90
+ === +dependency_cache_path+
108
91
 
109
- AWS S3 storage connections need +provider+, +aws_access_key_id+,
110
- +aws_secret_access_key+, and +region+.
92
+ The path where the dependency cache will be written
93
+ (<tt>dependency-cache.tar.*</tt>) for use in successive builds. Optional,
94
+ defaults to <tt>./tmp</tt>. Overridden with <tt>cartage --dependency-cache-path
95
+ PATH</tt>.
111
96
 
112
- plugins:
113
- s3:
114
- credentials:
115
- provider: AWS
116
- aws_access_key_id: YOUR_AWS_ACCESS_KEY_ID
117
- aws_secret_access_key: YOUR_AWS_SECRET_ACCESS_KEY
118
- region: us-west-2
97
+ On a CI system, this should be written somewhere that the CI system uses for
98
+ build caching.
119
99
 
120
- ===== Rackspace
100
+ dependency_cache_path: <%= ENV['SEMAPHORE_CACHE'] %>
121
101
 
122
- Rackspace storage connections need +provider+, +rackspace_username+,
123
- +rackspace_api_key+, and optionally +rackspace_auth_url+.
102
+ === +commands+
124
103
 
125
- plugins:
126
- s3:
127
- credentials:
128
- provider: Rackspace
129
- rackspace_username: RACKSPACE_USERNAME
130
- rackspace_api_key: RACKSPACE_API_KEY
131
- rackspace_auth_url: lon.auth.api.rackspacecloud.com
132
-
133
- ===== Google
134
-
135
- Google storage connections need +provider+, +google_storage_access_key_id+, and
136
- +google_storage_secret_access_key+.
137
-
138
- plugins:
139
- s3:
140
- credentials:
141
- provider: Google
142
- google_storage_access_key_id: YOUR_SECRET_ACCESS_KEY_ID
143
- google_storage_secret_access_key: YOUR_SECRET_ACCESS_KEY
104
+ This dictionary is for command-specific configuration. As of 2.0, none of the
105
+ commands provided by default or in existing plug-ins have command-specific
106
+ configuration. The keys are freeform and should be based on the *primary* name
107
+ of the command (so the <tt>cartage pack</tt> command should use the key
108
+ <tt>pack</tt>.)
144
109
 
145
- === cartage-remote
110
+ commands: {}
146
111
 
147
- Cartage-remote needs to know where its remote build is going to run and how to
148
- authenticate. It may also be told *what* to run. For cartage-remote 1.0, the
149
- +remote+ configuration section is *required* (at least the +server+ key).
150
-
151
- ==== +server+
152
-
153
- The name of the build server. This field is required and can show up in two
154
- different formats. The first is as a string matching
155
- <tt>[user@]host[:port]</tt>.
156
-
157
- plugins:
158
- remote:
159
- server: build@my-build-machine:2222
160
-
161
- The second is as a dictionary with +user+, +host+, and +port+ keys.
112
+ === +plugins+
162
113
 
163
- plugins:
164
- remote:
165
- server:
166
- user: build
167
- host: my-build-machine
168
- port: 2222
114
+ This dictionary is for plug-in-specific configuration. See each plug-in for
115
+ configuration options. The keys to the plug-ins are based on the plug-in name.
116
+ cartage-bundler is available as Cartage::Bundler; the transformed plug-in name
117
+ will be <tt>bundler</tt>.
169
118
 
170
- ==== +keys+
171
-
172
- The SSH key(s) used to connect to the server. Optional, and cartage-remote will
173
- use <tt>~/.ssh/*id_[rd]sa</tt> to find keys if this is not provided. The first
174
- format is as a dictionary, embedding private keys directly in the configuration
175
- file.
176
-
177
- plugins:
178
- remote:
179
- keys:
180
- custom: |
181
- -----BEGIN RSA PRIVATE KEY-----
182
- ...
183
- -----END RSA PRIVATE KEY-----
184
-
185
- The second form is as an array of glob patterns to match key files.
186
-
187
- plugins:
188
- remote:
189
- keys:
190
- - "config/secrets/*id_[rd]sa"
191
- - "~/.ssh/*id_[rd]sa"
192
-
193
- The third form is as a single glob pattern to match key files or a specific key
194
- file.
195
-
196
- plugins:
197
- remote:
198
- keys: "config/secrets/*id_[rd]sa"
199
-
200
- ==== +build+
201
-
202
- The build script that will be run on the remote server. This is optional with a
203
- reasonable default, below.
204
-
205
- #!/bin/bash
206
- set -e
207
- if [ -f Gemfile ]; then
208
- bundle install --path %<remote_bundle>s
209
- bundle exec cartage build \
210
- --config-file %<config_file>s \
211
- --target %<project_path>s
212
- else
213
- cartage build --config-file %<config_file>s \
214
- --target %<project_path>s
215
- fi
216
-
217
- An example with an alternate build script that uses cartage-s3 to upload.
218
-
219
- plugins:
220
- remote:
221
- build: |
222
- #!/bin/bash
223
- set -e
224
- if [ -f Gemfile ]; then
225
- bundle install --path %<remote_bundle>s
226
- bundle exec cartage s3 \
227
- --config-file %<config_file>s \
228
- --target %<project_path>s \
229
- --verbose
230
- else
231
- cartage build \
232
- --config-file %<config_file>s \
233
- --target %<project_path>s \
234
- --verbose
235
- fi
236
-
237
- ==== +prebuild+
238
-
239
- The prebuild script that will be run on the local system. This is optional with
240
- a reasonable default, below:
241
-
242
- #!/bin/bash
243
- ssh-keyscan -H %<remote_host>s >> ~/.ssh/known_hosts
244
-
245
- An example with a slightly extended example is below. It is strongly
246
- recommended that the <tt>ssh-keyscan</tt> step be run in all prebuild scripts
247
- as cartage-remote runs SSH in a paranoid mode.
248
-
249
- plugins:
250
- remote:
251
- prebuild: |
252
- #!/bin/bash
253
- ssh-keyscan -H %<remote_host>s >> ~/.ssh/known_hosts
254
- echo 'Prebuild complete'
255
-
256
- ==== +postbuild+
257
-
258
- The postbuild script that will be run on the local system. This is optional
259
- with no default (no post-build actions).
260
-
261
- The postbuild script will be passed the stage identifier as <tt>$1</tt>
262
- (valid values are +config+, +ssh_config+, +prebuild+, +remote_clone+,
263
- +remote_build+, +cleanup+, or +finished+). If the build was interrupted with
264
- an error, the error message will be passed as <tt>$2</tt>.
265
-
266
- An example that posts a message to Slack is below.
267
-
268
- plugins:
269
- remote:
270
- postbuild: |
271
- #!/bin/bash
272
- case ${1:-undefined} in
273
- finished)
274
- t="token=SLACK_TOKEN"
275
- c="channel=%23ci"
276
- d=MYDOMAIN
277
- u="https://${d}.slack.com/services/hooks/slackbot?${t}&${c}"
278
- curl --data "Build %<name>s-%<timestamp>s complete." ${u}
279
- ;;
280
- *)
281
- : # ${1} is the stage, ${2} is the error message
282
- ;;
283
- esac
119
+ plugins: {}
@@ -0,0 +1,71 @@
1
+ ## Contributing
2
+
3
+ We value any contribution to Cartage you can provide: a bug report, a feature
4
+ request, or code contributions. Cartage is reasonably complex code, so there
5
+ are a few guidelines:
6
+
7
+ * Changes *will not* be accepted without tests. The test suite is written
8
+ with [Minitest][].
9
+ * Match our coding style.
10
+ * Use a thoughtfully-named topic branch that contains your change. Rebase
11
+ your commits into logical chunks as necessary.
12
+ * Use [quality commit messages][].
13
+ * Do not change the version number; when your patch is accepted and a release
14
+ is made, the version will be updated at that point.
15
+ * Submit a GitHub pull request with your changes.
16
+ * New behaviours and features are probably better created as cartage plugins.
17
+ If they are best suited for the core Cartage features, they require new or
18
+ updated documentation.
19
+
20
+ ### Test Dependencies
21
+
22
+ Cartage uses Ryan Davis’s [Hoe][] to manage the release process, which adds a
23
+ number of rake tasks. You will mostly be interested in:
24
+
25
+ $ rake
26
+
27
+ which runs the tests the same way that:
28
+
29
+ $ rake test
30
+ $ rake travis
31
+
32
+ will do.
33
+
34
+ To assist with the installation of the development dependencies for Cartage, I
35
+ have provided the simplest possible Gemfile pointing to the (generated)
36
+ `cartage.gemspec` file. This will permit you to do:
37
+
38
+ $ bundle install
39
+
40
+ to get the development dependencies. If you aleady have `hoe` installed, you
41
+ can accomplish the same thing with:
42
+
43
+ $ rake newb
44
+
45
+ This task will install any missing dependencies, run the tests/specs, and
46
+ generate the RDoc.
47
+
48
+ ### Workflow
49
+
50
+ Here's the most direct way to get your work merged into the project:
51
+
52
+ * Fork the project.
53
+ * Clone down your fork (`git clone
54
+ git://github.com/KineticCafe/cartage.git`).
55
+ * Create a topic branch to contain your change (`git checkout -b
56
+ my_awesome_feature`).
57
+ * Hack away, add tests. Not necessarily in that order.
58
+ * Make sure everything still passes by running `rake`.
59
+ * If necessary, rebase your commits into logical chunks, without errors.
60
+ * Push the branch up (`git push origin my_awesome_feature`).
61
+ * Create a pull request against KineticCafe/cartage and describe
62
+ what your change does and the why you think it should be merged.
63
+
64
+ ### Contributors
65
+
66
+ * Austin Ziegler created Cartage.
67
+
68
+ [Minitest]: https://github.com/seattlerb/minitest
69
+ [quality commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
70
+ [Hoe]: https://github.com/seattlerb/hoe
71
+ [kccoc]: https://github.com/KineticCafe/code-of-conduct
@@ -0,0 +1,520 @@
1
+ # Extending Cartage
2
+
3
+ Cartage is designed for extension. There are two distinct *types* of extension
4
+ that can be written: *commands* and *plug-ins*. Most *command* extensions will
5
+ implement a *plug-in* extension, but Cartage can be extended with either
6
+ mechanism on its own. For both *commands* and *plug-ins* there is automatic
7
+ discovery and activation.
8
+
9
+ ## Command Extensions
10
+
11
+ Command extensions add new commands and subcommands to the Cartage CLI
12
+ (`bin/cartage`). The Cartage CLI is written with the [GLI][] DSL, so all of the
13
+ features present in GLI can be used to add commands to Cartage itself. Commands
14
+ are discovered by placing a command definition file in `lib/cartage/commands`
15
+ in the gem containing the command extension.
16
+
17
+ ### Defining a Command
18
+
19
+ New commands *extend* Cartage::CLI, opening a context where the GLI DSL can be
20
+ used to create a new command. Outside of the new command block, it is
21
+ recommended that only `desc`, `long_desc`, `arg`, and `command` be used since
22
+ there is no way of adding support for new global switches or flags.
23
+
24
+ ```ruby
25
+ Cartage::CLI.extend do
26
+ # Define the commands here.
27
+ desc 'A new command'
28
+ long_desc 'A long description for the new command'
29
+ arg :maybe, :optional
30
+ arg :yes # required by default
31
+ arg :items, :multiple
32
+ command 'newcommand' do |echo|
33
+ echo.desc 'Enable fuzz mode.'
34
+ echo.switch :fuzz
35
+
36
+ echo.desc 'Fuzz level.'
37
+ echo.flag 'fuzz-level', arg_name: :LEVEL, default_value: 0
38
+ echo.action do |global, options, args|
39
+ # Whatever the new command does should be done here.
40
+ end
41
+ end
42
+ end
43
+ ```
44
+
45
+ It is recommended that a command extension add only one new command with
46
+ subcommands to handle more complex operations.
47
+
48
+ ```ruby
49
+ Cartage::CLI.extend do
50
+ command 'newcommand' do |echo|
51
+ echo.command 'subcommand' do |sc|
52
+ sc.action do |global, options, args|
53
+ # subcommand actions
54
+ end
55
+ end
56
+
57
+ echo.default_command :subcommand
58
+ end
59
+ end
60
+ ```
61
+
62
+ ### Global Cartage Instance
63
+
64
+ Each defined command operates in the same global context and has access to a
65
+ method (`cartage`) that returns the operating instance of Cartage for the
66
+ execution of the `cartage` command.
67
+
68
+ ```ruby
69
+ Cartage::CLI.extend do
70
+ desc 'Echo the provided text'
71
+ arg :TEXT, :multiple
72
+ command 'echo' do |echo|
73
+ echo.desc 'Suppress newlines'
74
+ echo.switch [ :c, 'newlines' ], default: true, negatable: false
75
+ echo.action do |_g, options, args|
76
+ unless cartage.quiet
77
+ message = args.join(' ')
78
+ if options['no-newlines'] || cartage.config(for_command: 'echo').no_newlines
79
+ puts message
80
+ else
81
+ print message
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ In the example above, the `echo` command checks the global configuration for
90
+ the value of the `quiet` switch, and the command options and configuration for
91
+ whether newlines should be included in the output. Note the use of
92
+ Cartage#config with the `for_command` keyword parameter.
93
+
94
+ ### Exiting the Command
95
+
96
+ If the command is successful, completing its action block without raising an
97
+ exception will be sufficient. If an exception is thrown, it will be caught, its
98
+ message displayed, and `cartage` will exit with a non-zero exit status.
99
+
100
+ Cartage::CLI provides three *special* exceptions:
101
+
102
+ Cartage::CLI::QuietExit
103
+ : This will abort or exit the command with the provided exit status. No message
104
+ will be displayed. This is used in `cartage manifest check`.
105
+
106
+ Cartage::CLI::CustomExit
107
+ : An alias for GLI::CustomExit. Raised with a message and an exit status. The
108
+ message will be displayed, and the exit status will be returned. This should be
109
+ used for *failure* of the command.
110
+
111
+ Cartage::CLI::CommandException
112
+ : An alias for GLI::CommandException. This exception is similar to CustomExit,
113
+ but should be used when there is a configuration issue, or a conflict in flags
114
+ or switches. It requires three parameters: the message to display, the command
115
+ name for help purposes, and the exit status.
116
+
117
+ ### Example (`info` Command)
118
+
119
+ Below is an example command and sub-commands, `cartage info`. This has
120
+ limited utility, but will illustrate how commands are created in Cartage. This
121
+ example is usable, as the `info` command is a hidden command on `cartage`.
122
+
123
+ ```ruby
124
+ # Call Cartage::CLI.extend to add the commands defined in the block.
125
+ Cartage::CLI.extend do
126
+ # Use +desc+ to provide a description about the next command.
127
+ desc 'Show information about Cartage itself'
128
+
129
+ # Use +long_desc+ to provide a long description about the next command that
130
+ # can # be included in a generated RDoc file.
131
+ long_desc <<-'DESC'
132
+ Shows various bits of information about Cartage based on the running instance.
133
+ Some plug-ins do run-time resolution of configuration values, so what is shown
134
+ through these subcommands will not represent that resolution.
135
+ DESC
136
+
137
+ # Declare a new command with +command+ and one or more names for the command.
138
+ # This command is available as both <tt>cartage info</tt> and <tt>cartage
139
+ # i</tt>. The block contains the definition of the switches, flags,
140
+ # subcommands, and actions for this command.
141
+ command %w(info i) do |info|
142
+ # The same GLI::DSL works on the command being created, Here, we are
143
+ # creating <tt>cartage info plugins</tt>.
144
+ info.desc 'Show the plug-ins that have been found.'
145
+ info.long_desc <<-'DESC'
146
+ Only plug-ins using the class plug-in protocol are found this way. Plug-ins
147
+ that just provide commands are not reported as plugins.
148
+ DESC
149
+ info.command 'plugins' do |plugins|
150
+ # Implement the #action as a block. It accepts three parameters: the
151
+ # global options hash, the local command options hash, and any arguments
152
+ # passed on the command-line.
153
+ #
154
+ # Within a command's action, a +cartage+ object is available that
155
+ # represents the Cartage instance that will be used for packaging.
156
+ plugins.action do |_global, _options, _args|
157
+ plugs = cartage.plugins.enabled
158
+ if plugs.empty?
159
+ puts 'No active plug-ins.'
160
+ else
161
+ plugs.map! { |plug| "* #{plug.plugin_name} (#{plug.version})" }
162
+ puts <<-plugins
163
+ Active Plug-ins:
164
+
165
+ #{plugs.join("\n")}
166
+ plugins
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ ```
173
+
174
+ ## Plug-In Extensions
175
+
176
+ Cartage plug-ins:
177
+
178
+ * are automatically discovered and loaded;
179
+ * are accessible from and initialized with an owning Cartage instance;
180
+ * have a namespace in the Cartage configuration file; and
181
+ * may implement feature requests or offers (see below).
182
+
183
+ When explaining how to build plug-ins, we will be examining three different
184
+ plug-ins:
185
+
186
+ * Cartage::Manifest manages the `Manifest.txt` file and the `.cartignore`
187
+ file that indicates what files will be included in the release package, and
188
+ is for use with the `cartage manifest` family of commands.
189
+
190
+ * No configuration
191
+ * Bundled with Cartage (`lib/cartage/plugins/manifest.rb`)
192
+
193
+ * Cartage::BuildTarball is responsible for building the final package after
194
+ Cartage prepares the work area.
195
+
196
+ * No configuration
197
+ * Implements feature requests
198
+ * Implements feature offers
199
+ * Bundled with Cartage (`lib/cartage/plugins/build_tarball.rb`)
200
+
201
+ * Cartage::Bundler is responsible for vendoring Bundler dependencies for Ruby
202
+ projects. It was originally part of Cartage 1.x, but has been extracted to
203
+ its own gem.
204
+
205
+ * Has configuration
206
+ * Implements feature offers
207
+ * Gem [cartage-bundler][] (`lib/cartage/plugins/bundler.rb`)
208
+
209
+ ### Plug-In Discovery and Initialization
210
+
211
+ Plug-ins are placed in files that are found in `lib/cartage/plugins` and are
212
+ found in the `$LOAD_PATH`[^1]. The code in these files should define a class
213
+ that inherits from Cartage::Plugin.
214
+
215
+ When a Cartage instance is created, the plug-in will be loaded and the
216
+ resulting plug-in class will be added to the instance as a method matching the
217
+ name of the class of the plug-in. Given an instance of Cartage called
218
+ `cartage`:
219
+
220
+ * Cartage::Manifest is available as `cartage.manifest`.
221
+ * Cartage::BuildTarball is available as `cartage.build_tarball`.
222
+ * Cartage::Bundler is available as `cartage.bundler`.
223
+
224
+ ### Plug-In Configuration
225
+
226
+ Each plug-in has its own configuration dictionary as part of the
227
+ Cartage::Config dictionary, under the `plugins` dictionary. When the Cartage
228
+ configuration file is loaded and resolved for Cartage, the relevant section of
229
+ the Cartage dictionary is provided to the plug-in.
230
+
231
+ Any plug-in's configuration dictionary can be requested through Cartage#config
232
+ with the `for_plugin` keyword parameter:
233
+
234
+ ```ruby
235
+ cartage.config(for_plugin: :bundler)
236
+ ```
237
+
238
+ Plug-ins should implement `#resolve_plugin_config!` to finalize configuration
239
+ as appropriate.
240
+
241
+ * Cartage::Manifest only uses core Cartage configuration.
242
+ * Cartage::BuildTarball only uses core Cartage configuration.
243
+ * Cartage::Bundler uses its configuration section. For full details, read the
244
+ documentation of [`cartage-bundler`][]. `#resolve_plugin_config!` resolves
245
+ the Gemfile and the gem group exclusions.
246
+
247
+ ```yaml
248
+ ---
249
+ commands: {}
250
+ plugins:
251
+ bundler:
252
+ without_groups:
253
+ - assets
254
+ - development
255
+ - test
256
+ ```
257
+
258
+ This is handled with:
259
+
260
+ ```ruby
261
+ # Cartage::Bundler is a +vendor_dependencies+ plug-in for Cartage.
262
+ class Cartage::Bundler < Cartage::Plugin
263
+ private
264
+
265
+ attr_reader :gemfile, :without_groups
266
+
267
+ def resolve_plugin_config!(config)
268
+ @gemfile = cartage.root_path.join(config.gemfile || 'Gemfile').expand_path
269
+ @without_groups = Array(config.without_groups || %w(development test))
270
+ end
271
+ end
272
+ ```
273
+
274
+ ### Disabling Plug-Ins
275
+
276
+ All discovered plug-ins are enabled by default and can be disabled in the
277
+ plug-in's configuration dictionary with the key `disabled`. Note that disabling
278
+ a plug-in only has meaning for plug-ins that implement feature offers, as
279
+ disabled plug-ins will be skipped when features are requested.
280
+
281
+ ```yaml
282
+ ---
283
+ plugins:
284
+ bundler:
285
+ disabled: true
286
+ ```
287
+
288
+ Plug-ins may have additional conditions that may cause them to be disabled.
289
+ Cartage::Bundler is disabled if the `Gemfile` does not exist.
290
+
291
+ ```ruby
292
+ class Cartage::Bundler < Cartage::Plugin
293
+ # Cartage::Bundler is only enabled if the Gemfile exists.
294
+ def disabled?
295
+ super || !gemfile.exist?
296
+ end
297
+ end
298
+ ```
299
+
300
+ ### Feature Requests and Offers
301
+
302
+ Cartage plug-ins can implement feature *requests* or *offers* to provide
303
+ functionality that is loosely-coupled and dynamically discovered. Cartage
304
+ commands or plug-ins will broadcast feature *requests* to all enabled plug-ins;
305
+ any plug-in which *offers* that feature will then have appropriate methods
306
+ called to perform the plug-in's implementation of that feature.
307
+
308
+ A *feature* is simply a label that plug-ins *request* or *offer*. An example is
309
+ useful, based on Cartage#build_package (run by `cartage pack`).
310
+
311
+ 1. Cartage *requests* `:vendor_dependencies`. Cartage::Bundler *offers*
312
+ `:vendor_dependencies`. In turn, `#vendor_dependencies` (which ultimately
313
+ runs `bundle install`) and `#path` will be called on Cartage::Bundler.
314
+
315
+ 2. Cartage *requests* `:pre_build_package`.
316
+
317
+ 3. Cartage *requests* `:build_package`. Cartage::BuildTarball *offers*
318
+ `:build_package`, so `#build_package` will be called.
319
+
320
+ 4. Cartage::BuildTarball *requests* `:pre_build_tarball`.
321
+
322
+ 5. Cartage::BuildTarball builds the tarball.
323
+
324
+ 6. Cartage::BuildTarball *requests* `:post_build_tarball`.
325
+
326
+ 7. Cartage *requests* `:post_build_package`.
327
+
328
+ There is no fixed set of features, so any plug-in can request any feature. When
329
+ feature `:build_package` is requested (see [Requesting Features][]), the
330
+ collection of plugins is filtered for plugins that `#offer?(:build_package)`.
331
+
332
+ #### Offering Features
333
+
334
+ Most plug-ins will *offer* features. The easiest way to offer a particular
335
+ feature is to implement a public method that matches the name of the requested
336
+ feature[^2]. This is visible in Cartage::Bundler with `#vendor_dependencies`
337
+ and Cartage::BuildTarball with `#build_package`.
338
+
339
+ This works because feature *requests* will generally want to call a method of
340
+ the same name as the feature. The implementation of the `#offer?` test on
341
+ Cartage::Plugin makes this explicit.
342
+
343
+ ```ruby
344
+ class Cartage::Plugin
345
+ def offer?(name)
346
+ enabled? && offer_feature?(name.to_sym)
347
+ end
348
+
349
+ private
350
+
351
+ def offer_feature?(name)
352
+ respond_to?(name)
353
+ end
354
+ end
355
+ ```
356
+
357
+ #### Requesting Features
358
+
359
+ Some plug-ins will *request* features. As shown in the outline above,
360
+ Cartage::BuildTarball *offers* the `:build_package` feature but *requests* two
361
+ additional features, `:pre_build_tarball` and `:post_build_tarball`. It does
362
+ this with Cartage::Plugins#request.
363
+
364
+ ```ruby
365
+ class Cartage::BuildTarball < Cartage::Plugin
366
+ def build_package
367
+ cartage.plugins.request(:pre_build_tarball)
368
+ # build the tarball here
369
+ cartage.plugins.request(:post_build_tarball)
370
+ end
371
+ end
372
+ ```
373
+
374
+ Cartage::Plugins#request selects plugins that *offer* the feature, and then
375
+ loops over the selected plug-ins calling the feature method against them. The
376
+ implementation of #request is flexible enough to allow for a feature to be
377
+ requested and a different method to be called. This makes
378
+ Cartage::BuildTarball#build_package implementation look like:
379
+
380
+ ```ruby
381
+ class Cartage::BuildTarball < Cartage::Plugin
382
+ def build_package
383
+ cartage.plugins.request(:pre_build_tarball, :pre_build_tarball)
384
+ # build the tarball here
385
+ cartage.plugins.request(:post_build_tarball, :pre_build_tarball)
386
+ end
387
+ end
388
+ ```
389
+
390
+ This works for a feature that requires multiple phases or steps, like
391
+ `:vendor_dependencies` does:
392
+
393
+ ```ruby
394
+ class Cartage
395
+ private
396
+
397
+ def vendor_dependencies
398
+ extract_dependency_cache
399
+
400
+ plugins.request(:vendor_dependencies)
401
+
402
+ create_dependency_cache(
403
+ plugins.request_map(:vendor_dependencies, :path).compact.flatten
404
+ )
405
+ end
406
+ end
407
+ ```
408
+
409
+ 1. Features can be requested using methods other than the name of the feature.
410
+ `:vendor_dependencies` requires both `#vendor_dependencies` and `#path`.
411
+ 2. Cartage::Plugins#request_map is used to return the collection of results
412
+ from the plug-ins *offering* the feature requested.
413
+
414
+ ### *Feature Requests* from Cartage
415
+
416
+ The following *feature requests* are made when running Cartage:
417
+
418
+ #### `:vendor_dependencies`
419
+
420
+ Used during the setup of the work area to install external dependencies in the
421
+ package directory. Since external dependencies are usually described by a file,
422
+ a plug-in that offers `:vendor_dependencies` should disable itself if the
423
+ descriptive file cannot be found (such as the `Gemfile` or `package.json`).
424
+
425
+ > [cartage-bundler][] offers this for Ruby Bundler-based packages.
426
+
427
+ __Methods Required__
428
+
429
+ * __`#vendor_dependencies`__: Invokes the tool that installs the external
430
+ dependencies. This might run `bundle install` (for Ruby Bundler) or `npm
431
+ install` (for Node.js NPM). It should be run in a way that only includes
432
+ production dependencies, not development dependencies.
433
+
434
+ * __`#path`__: Indicates the directory or directories, relative to the work
435
+ area, where the external dependencies are installed. This might be
436
+ `vendor/bundle` (for Ruby Bundler) or `node_modules` (for Node.js NPM).
437
+
438
+ #### `:pre_build_package`
439
+
440
+ Used to perform any other work that must be done for the contents of the
441
+ package to be ready for packaging. A plug-in could be written to perform asset
442
+ precompilation prior to packaging, for example.
443
+
444
+ Plug-ins offering `:pre_build_package` __should__ offer `:post_build_package`.
445
+
446
+ __Methods Required__
447
+
448
+ * __`#pre_build_package`__: Performs the actions required to finalize package
449
+ preparation.
450
+
451
+ #### `:post_build_package`
452
+
453
+ Used to perform any other work that must be done for the contents of the
454
+ package to be complete. A plug-in could be written to cryptographically sign
455
+ built packages, for example.
456
+
457
+ __Methods Required__
458
+
459
+ * __`#post_build_package`__: Performs the actions required after package
460
+ creation.
461
+
462
+ If actions should be performed against any created packages, this should
463
+ request `:build_package#package_name`.
464
+
465
+ ```ruby
466
+ cartage.plugins.request_map(:build_package, :package_name)
467
+ ```
468
+
469
+ #### `:build_package`
470
+
471
+ Used to create a release package.
472
+
473
+ > This offered by Cartage as Cartage::BuildTarball.
474
+
475
+ __Methods Required__
476
+
477
+ * __`#build_package`__: Performs the actions required to build a package for
478
+ a particular format.
479
+
480
+ * __`#package_name`__: Returns the name of the package that will be created.
481
+
482
+ #### `:pre_build_tarball`
483
+
484
+ Used to perform any other work that must be done for the contents of the
485
+ tarball to be ready for packaging.
486
+
487
+ Plug-ins offering `:pre_build_tarball` __should__ offer an implementation of
488
+ `:post_build_tarball` that reverses the effects of `:pre_build_tarball`. In
489
+ this way, side-effects between two `:build_package` plug-ins do not occur.
490
+
491
+ > Requested by Cartage::BuildTarball#build_package.
492
+
493
+ __Methods Required__
494
+
495
+ * __`#pre_build_tarball`__: Performs the actions required before building the
496
+ tarball.
497
+
498
+ #### `:post_build_tarball`
499
+
500
+ Used to perform any other work that must be after creating the tarball.
501
+ Recommended to revert actions performed in `:pre_build_tarball` plug-ins.
502
+
503
+ > Requested by Cartage::BuildTarball#build_package.
504
+
505
+ __Methods Required__
506
+
507
+ * __`#post_build_tarball`__: Performs the actions required after building the
508
+ tarball.
509
+
510
+ [GLI]: https://github.com/davetron5000/gli
511
+ [cartage-bundler]: https://github.com/KineticCafe/cartage-bundler
512
+ [Requesting Features]: #label-Requesting+Features
513
+
514
+ [^1]: Plug-ins are discovered using Rubygems APIs, where commands are
515
+ discovered using `$LOAD_PATH`. While there is a difference, it has little
516
+ practical impact.
517
+
518
+ [^2]: This may not be sufficient for all requested features. The
519
+ `:vendor_dependencies` feature requires both `#vendor_dependencies` and
520
+ `#path` be implemented.