cartage-remote 1.1 → 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: 8d8ac39718d7abe3c0091abb06c8d3ecb456626e
4
- data.tar.gz: a143524b851528d44b9dde9f77a9946c76ca2b1e
3
+ metadata.gz: a407715cfc69a80ee22680f2bd2596987503a27f
4
+ data.tar.gz: aa30045f89581ac22feb52783e326cbfb43c26d0
5
5
  SHA512:
6
- metadata.gz: 7a36d402c39713f5e0c1d4337cc52e7a7d57ddf74db363add86ca1e0c8db08d876603b58c1e099f8782507dd3ee9fec0fcce42311ccab03502f73aed318d0814
7
- data.tar.gz: 395e90926c44b60f527398084938f8755679110c2b404211975d74077068a5881598f8b504c54e7c2d50705c1af829561242a0c6f03e422010f10183412eb97d
6
+ metadata.gz: 790d60ff41276adef303b1b3dac7374a688db17cb3c7db93b32dafcfbb775c3410c8bb41c097ba133ce4baacbf552c2d37f4b7957ceb90756844c1062da2257d
7
+ data.tar.gz: 3db9359ef4e88f7fe29e999009c72364518c38671eb4fa55e0dc85505d97cfce33ea1018efc13b31ae9ad04c55a278259c8de2956f0b6ffccf512e22e1b1cb57
@@ -0,0 +1,68 @@
1
+ ## Contributing
2
+
3
+ We value any contribution to cartage-remote you can provide: a bug report, a
4
+ feature request, or code contributions. cartage-remote is reasonably complex
5
+ code, so there are a few contribution 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
+
17
+ ### Test Dependencies
18
+
19
+ cartage-remote uses Ryan Davis’s [Hoe][] to manage the release process, and it
20
+ adds a number of rake tasks. You will mostly be interested in:
21
+
22
+ $ rake
23
+
24
+ which runs the tests the same way that:
25
+
26
+ $ rake test
27
+ $ rake travis
28
+
29
+ will do.
30
+
31
+ To assist with the installation of the development dependencies for
32
+ cartage-remote, I have provided the simplest possible Gemfile pointing to the
33
+ (generated) `cartage-remote.gemspec` file. This will permit you to do:
34
+
35
+ $ bundle install
36
+
37
+ to get the development dependencies. If you aleady have `hoe` installed, you
38
+ can accomplish the same thing with:
39
+
40
+ $ rake newb
41
+
42
+ This task will install any missing dependencies, run the tests/specs, and
43
+ generate the RDoc.
44
+
45
+ ### Workflow
46
+
47
+ Here's the most direct way to get your work merged into the project:
48
+
49
+ * Fork the project.
50
+ * Clone down your fork (`git clone
51
+ git://github.com/KineticCafe/cartage-remote.git`).
52
+ * Create a topic branch to contain your change (`git checkout -b
53
+ my_awesome_feature`).
54
+ * Hack away, add tests. Not necessarily in that order.
55
+ * Make sure everything still passes by running `rake`.
56
+ * If necessary, rebase your commits into logical chunks, without errors.
57
+ * Push the branch up (`git push origin my_awesome_feature`).
58
+ * Create a pull request against KineticCafe/cartage-remote and describe
59
+ what your change does and the why you think it should be merged.
60
+
61
+ ### Contributors
62
+
63
+ * Austin Ziegler created cartage-remote.
64
+
65
+ [Minitest]: https://github.com/seattlerb/minitest
66
+ [quality commit messages]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
67
+ [Hoe]: https://github.com/seattlerb/hoe
68
+ [kccoc]: https://github.com/KineticCafe/code-of-conduct
@@ -1,3 +1,14 @@
1
+ === 2.0 / 2016-03-DD
2
+
3
+ * Rewrote for compatibility with cartage 2.0.
4
+
5
+ * 1 major enhancements
6
+
7
+ * Configuration now supports multiple servers. Old cartage-remote
8
+ configurations will continue to work and be accessible as the +default+
9
+ host. An error will be raised if there is an explicit +default+
10
+ host combined with this implicit +default+ host.
11
+
1
12
  === 1.1 / 2015-03-26
2
13
 
3
14
  * 1 major bugfix
@@ -1,8 +1,8 @@
1
- == Licence
1
+ ## Licence
2
2
 
3
3
  This software is available under an MIT-style licence.
4
4
 
5
- * Copyright 2015 Kinetic Cafe
5
+ * Copyright 2015–2016 Kinetic Cafe
6
6
 
7
7
  Permission is hereby granted, free of charge, to any person obtaining a copy of
8
8
  this software and associated documentation files (the "Software"), to deal in
@@ -11,9 +11,9 @@ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
11
11
  of the Software, and to permit persons to whom the Software is furnished to do
12
12
  so, subject to the following conditions:
13
13
 
14
- * The names of its contributors may not be used to endorse or promote
15
- products derived from this software without specific prior written
16
- permission.
14
+ * The names of its contributors may not be used to endorse or promote
15
+ products derived from this software without specific prior written
16
+ permission.
17
17
 
18
18
  The above copyright notice and this permission notice shall be included in all
19
19
  copies or substantial portions of the Software.
@@ -1,12 +1,11 @@
1
- .autotest
2
- .gemtest
3
- .minitest.rb
4
- Contributing.rdoc
5
- Gemfile
6
- History.rdoc
7
- Licence.rdoc
1
+ Contributing.md
2
+ History.md
3
+ Licence.md
8
4
  Manifest.txt
9
5
  README.rdoc
10
6
  Rakefile
11
- lib/cartage/remote.rb
12
- lib/cartage/remote/command.rb
7
+ lib/cartage/commands/remote.rb
8
+ lib/cartage/plugins/remote.rb
9
+ lib/cartage/remote/host.rb
10
+ test/minitest_config.rb
11
+ test/test_cartage_remote.rb
@@ -2,7 +2,7 @@
2
2
 
3
3
  code :: https://github.com/KineticCafe/cartage-remote/
4
4
  issues :: https://github.com/KineticCafe/cartage-remote/issues
5
- continuous integration :: {<img src="https://travis-ci.org/KineticCafe/cartage-remote.png" />}[https://travis-ci.org/KineticCafe/cartage-remote]
5
+ continuous integration :: {<img src="https://travis-ci.org/KineticCafe/cartage-remote.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/KineticCafe/cartage-remote]
6
6
 
7
7
  == Description
8
8
 
@@ -11,20 +11,35 @@ to build a package on a remote machine with cartage.
11
11
 
12
12
  Cartage provides a repeatable means to create a package for a Rails application
13
13
  that can be used in deployment with a configuration tool like Ansible, Chef,
14
- Puppet, or Salt. The package is created with its dependencies bundled in
15
- `vendor/bundle`, so it can be deployed in environments with strict access
16
- control rules and without requiring development tool access.
14
+ Puppet, or Salt.
17
15
 
18
- == Synopsis
16
+ == Synopsis & Configuration
17
+
18
+ cartage-remote is a plug-in for Cartage that builds a package on a remote
19
+ machine.
19
20
 
20
21
  # Build a package on a remote machine via SSH.
21
22
  cartage remote
22
23
 
24
+ This can be configured in the <tt>config.remote</tt> section of the Cartage
25
+ configuration file.
26
+
27
+ plugins:
28
+ remote:
29
+ disabled: false
30
+ host: default
31
+ hosts:
32
+ default:
33
+ user: build
34
+ address: build-machine
35
+ port: 22
36
+ alternate: alternate@build-machine:2222
37
+
23
38
  == Install
24
39
 
25
40
  Add cartage-remote to your Gemfile:
26
41
 
27
- gem 'cartage-remote', '~> 1.1'
42
+ gem 'cartage-remote', '~> 2.0'
28
43
 
29
44
  Or manually install:
30
45
 
@@ -37,9 +52,13 @@ change:
37
52
 
38
53
  * When PATCH is zero (+0+), it will be omitted from version references.
39
54
 
40
- cartage-remote will generally track cartage for major versions to ensure plugin API
41
- compatibility.
55
+ cartage-remote will generally track cartage for major versions to ensure plugin
56
+ API compatibility.
42
57
 
43
- :include: Contributing.rdoc
58
+ == Community and Contributing
44
59
 
45
- :include: Licence.rdoc
60
+ cartage-bundler welcomes your contributions as described in
61
+ {Contributing.md}[https://github.com/KineticCafe/cartage-bundler/blob/master/Contributing.md].
62
+ This project, like all Kinetic Cafe {open source
63
+ projects}[https://github.com/KineticCafe], is under the Kinetic Cafe Open
64
+ Source {Code of Conduct}[https://github.com/KineticCafe/code-of-conduct].
data/Rakefile CHANGED
@@ -1,53 +1,70 @@
1
- # -*- ruby -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  require 'rubygems'
4
4
  require 'hoe'
5
- require 'pathname'
5
+ require 'rake/clean'
6
6
 
7
7
  Hoe.plugin :doofus
8
- Hoe.plugin :email unless ENV['CI'] or ENV['TRAVIS']
8
+ Hoe.plugin :email unless ENV['CI'] || ENV['TRAVIS']
9
9
  Hoe.plugin :gemspec2
10
10
  Hoe.plugin :git
11
11
  Hoe.plugin :minitest
12
12
  Hoe.plugin :rubygems
13
+ Hoe.plugin :travis
13
14
 
14
15
  spec = Hoe.spec 'cartage-remote' do
15
16
  developer('Austin Ziegler', 'aziegler@kineticcafe.com')
16
17
 
17
- self.history_file = 'History.rdoc'
18
+ self.history_file = 'History.md'
18
19
  self.readme_file = 'README.rdoc'
19
- self.extra_rdoc_files = FileList['*.rdoc'].to_a
20
20
 
21
21
  license 'MIT'
22
22
 
23
- self.extra_deps << ['cartage', '~> 1.1']
24
- self.extra_deps << ['micromachine', '~> 1.2']
25
- self.extra_deps << ['fog', '~> 1.27']
26
-
27
- self.extra_dev_deps << ['rake', '~> 10.0']
28
- self.extra_dev_deps << ['hoe-doofus', '~> 1.0']
29
- self.extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
30
- self.extra_dev_deps << ['hoe-git', '~> 1.5']
31
- self.extra_dev_deps << ['hoe-geminabox', '~> 0.3']
32
- self.extra_dev_deps << ['minitest', '~> 5.4']
33
- self.extra_dev_deps << ['minitest-autotest', '~> 1.0']
34
- self.extra_dev_deps << ['minitest-bisect', '~> 1.2']
35
- self.extra_dev_deps << ['minitest-focus', '~> 1.1']
36
- self.extra_dev_deps << ['minitest-moar', '~> 0.0']
37
- self.extra_dev_deps << ['minitest-pretty_diff', '~> 0.1']
38
- self.extra_dev_deps << ['simplecov', '~> 0.7']
23
+ ruby20!
24
+
25
+ extra_deps << ['cartage', '~> 2.0.rc1']
26
+ extra_deps << ['micromachine', '~> 2.0']
27
+ extra_deps << ['fog', '~> 1.27']
28
+ extra_deps << ['net-ssh', '~> 3.0']
29
+ extra_deps << ['net-scp', '~> 1.2']
30
+
31
+ extra_dev_deps << ['rake', '>= 10.0']
32
+ extra_dev_deps << ['rdoc', '~> 4.2']
33
+ extra_dev_deps << ['hoe-doofus', '~> 1.0']
34
+ extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
35
+ extra_dev_deps << ['hoe-git', '~> 1.5']
36
+ extra_dev_deps << ['hoe-travis', '~> 1.2']
37
+ extra_dev_deps << ['minitest', '~> 5.4']
38
+ extra_dev_deps << ['minitest-autotest', '~> 1.0']
39
+ extra_dev_deps << ['minitest-bisect', '~> 1.2']
40
+ extra_dev_deps << ['minitest-bonus-assertions', '~> 2.0']
41
+ extra_dev_deps << ['minitest-focus', '~> 1.1']
42
+ extra_dev_deps << ['minitest-moar', '~> 0.0']
43
+ extra_dev_deps << ['minitest-pretty_diff', '~> 0.1']
44
+ extra_dev_deps << ['simplecov', '~> 0.7']
45
+ end
46
+
47
+ ENV['RUBYOPT'] = '-W0'
48
+
49
+ module Hoe::Publish #:nodoc:
50
+ alias __make_rdoc_cmd__cartage__ make_rdoc_cmd
51
+
52
+ def make_rdoc_cmd(*extra_args) # :nodoc:
53
+ spec.extra_rdoc_files.delete_if { |f| f == 'Manifest.txt' }
54
+ __make_rdoc_cmd__cartage__(*extra_args)
55
+ end
39
56
  end
40
57
 
41
58
  namespace :test do
42
- task :coverage do
43
- prelude = <<-EOS
44
- require 'simplecov'
45
- SimpleCov.start('test_frameworks') { command_name 'Minitest' }
46
- gem 'minitest'
47
- EOS
48
- spec.test_prelude = prelude.split($/).join('; ')
49
- Rake::Task['test'].execute
59
+ if File.exist?('.simplecov-prelude.rb')
60
+ task :coverage do
61
+ spec.test_prelude = 'load ".simplecov-prelude.rb"'
62
+
63
+ Rake::Task['test'].execute
64
+ end
50
65
  end
66
+
67
+ CLOBBER << 'coverage'
51
68
  end
52
69
 
53
- # vim: syntax=ruby
70
+ CLOBBER << 'tmp'
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ Cartage::CLI.extend do
4
+ desc 'Build packages on remote servers'
5
+ long_desc <<-'DESC'
6
+ Resolve Cartage configuration locally, then execute a build script on the
7
+ configured remote server based on the resolved configuration.
8
+ DESC
9
+ command 'remote' do |remote|
10
+ remote.desc 'The name of the remote server to use'
11
+ remote.long_desc <<-'DESC'
12
+ The name of the defined remote server in the Cartage configuration file. If the
13
+ server does not exist in the configuration, an error will be reported.
14
+ DESC
15
+ remote.flag %i(H host), arg_name: :HOST, default_value: :default
16
+
17
+ remote.desc 'Check plugin configuration'
18
+ remote.long_desc <<-'DESC'
19
+ Verifies the configuration of the cartage-remote section of the Cartage
20
+ configuration file.
21
+ DESC
22
+ remote.command 'check-config' do |check|
23
+ check.hide!
24
+ check.action do |_global, _options, _args|
25
+ cartage.remote.check_config
26
+ end
27
+ end
28
+
29
+ remote.default_desc 'Run a build script remotely'
30
+ remote.action do |_global, options, _args|
31
+ options[:host] = nil if options[:host] == :default
32
+ config = cartage.config(for_plugin: :remote)
33
+ server = (options[:host] || config.host || :default).to_sym
34
+
35
+ unless config.dig(:hosts, server)
36
+ message = if options[:host] || config.host
37
+ "Host '#{server}' does not exist."
38
+ else
39
+ 'Default host does not exist.'
40
+ end
41
+ fail ArgumentError, message
42
+ end
43
+
44
+ config.server = server
45
+ cartage.remote.build
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,475 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+ require 'micromachine'
5
+ require 'cartage/plugin'
6
+
7
+ # A reliable way to create packages.
8
+ class Cartage
9
+ # Connect to a remote machine and build a package remotely. cartage-remote
10
+ # uses Fog::SSH with key-based authentication (not password-based) to connect
11
+ # to a remote server.
12
+ #
13
+ # cartage-remote assumes a relatively stable build server, but does not
14
+ # require one (custom +prebuild+ and +postbuild+ scripts could be used to
15
+ # manage that).
16
+ #
17
+ # == Remote Build Isolation
18
+ #
19
+ # cartage-remote allows for safe builds across multiple projects and branches
20
+ # with path-based isolation. The pattern for the build path is shown below,
21
+ # where the last part of the path is where the code will be cloned to.
22
+ #
23
+ # ~<remote_user>/cartage/<project-name>/<timestamp>/<project-name>
24
+ # | | | | |
25
+ # v | | | |
26
+ # build root v | | |
27
+ # cartage | | |
28
+ # path v | |
29
+ # project v |
30
+ # path isolation |
31
+ # path v
32
+ # build
33
+ # path
34
+ #
35
+ # So that if I am deploying on a project called +calliope+ and my remote user
36
+ # is +build+, my isolated build path might be:
37
+ #
38
+ # ~build/cartage/calliope/20160321091432/calliope
39
+ #
40
+ # == Remote Build Steps
41
+ #
42
+ # The steps for a remote build are:
43
+ #
44
+ # 1. Configure Cartage and save the active Cartage configuration as a
45
+ # temporary file that will be copied to the remote server.
46
+ # 2. Configure the Fog::SSH and Fog::SCP adapters with the keys to connect
47
+ # to the remote system.
48
+ # 3. Create the +prebuild+ script and run it locally (where the cartage CLI
49
+ # was run).
50
+ # 4. Connect to the remote server, put the Cartage configuration file in the
51
+ # isolation path, and clone the repository. Check the repo out to the
52
+ # appropriate +release_hashref+.
53
+ # 5. Create the +build+ script, copy it remotely, and run it from the build
54
+ # isolation path (+build_path+). This is effectively:
55
+ # cd "$build_path" && $build_script
56
+ # 6. Clean up the remote server fromt his build.
57
+ # 7. Create the +postbuild+ script and run it locally (where the cartage CLI
58
+ # was run).
59
+ #
60
+ # == Configuration
61
+ #
62
+ # cartage-remote is configured in the +plugins.remote+ section of the Cartage
63
+ # configuration file. It supports two primary keys:
64
+ #
65
+ # +hosts+:: A dictionary of hosts, as described below, that indicate the
66
+ # remote machine where the build script will be run. The host keys
67
+ # will be used as the +host+ value.
68
+ # +host+:: The name of the target host to be used. If missing, uses the
69
+ # +default+ location.
70
+ #
71
+ # For backwards compatibility, a single host may be specified in a +server+
72
+ # key, using the same format as the +host+ values. This host will become the
73
+ # +default+ host unless one is already specified in the +hosts+ dictionary
74
+ # (which is an error).
75
+ #
76
+ # The following keys are optional and may be provided globally in
77
+ # +plugins.remote+, or per host in +plugins.remote.hosts.$name+. If provided,
78
+ # host-level values override the global configuration.
79
+ #
80
+ # +keys+:: The SSH key(s) used to connect to the server. There are two basic
81
+ # ways that keys can be provided:
82
+ #
83
+ # * If provided as a string or an array of strings, the value(s)
84
+ # will be applied as glob patterns to find key files on disk.
85
+ # * If provided as a dictionary, the values are the ASCII
86
+ # representations of the private keys.
87
+ #
88
+ # If keys are not provided, keys will be found on the local machine
89
+ # using the pattern <tt>~/.ssh/*id_[rd]sa</tt>.
90
+ # +build+:: A multiline YAML string that is copied to the remote machine and
91
+ # executed as a script there. If not provided, the following script
92
+ # will be run:
93
+ #
94
+ # #!/bin/bash
95
+ # set -e
96
+ # if [ -f Gemfile ]; then
97
+ # bundle install --path %{remote_bundle}
98
+ # bundle exec cartage build \
99
+ # --config-file %{config_file} \
100
+ # --target %{project_path}
101
+ # else
102
+ # cartage build --config-file %{config_file} \
103
+ # --target %{project_path}
104
+ # fi
105
+ # +prebuild+:: A multiline YAML string that is run as a script on the local
106
+ # machine to prepare for running remotely. If not provided, the
107
+ # following script will be run:
108
+ #
109
+ # #!/bin/bash
110
+ # ssh-keyscan -H %{remote_adddress} >> ~/.ssh/known_hosts
111
+ # +postbuild+:: A multiline YAML string that is run as a script on the local
112
+ # machine to finish the build process locally. There is no
113
+ # default postbuild script. The script will be passed the stage
114
+ # (+local_config+, +ssh_config+, +prebuild+, +remote_clone+,
115
+ # +remote_build+, +cleanup+, or +finished+) and, if the stage
116
+ # is not +finished+, the error message.
117
+ #
118
+ # === Hosts
119
+ #
120
+ # A host describes the remote server. It may be specified either as a string
121
+ # in the form <tt>[user@]host[:port]</tt> *or* a dictionary with required
122
+ # keys:
123
+ #
124
+ # +user+:: The user to connect to the remote server as. If not provided,
125
+ # defaults to +$USER+.
126
+ # +address+:: The host address for connecting to the remote server. Also
127
+ # called +host+.
128
+ # +port+:: The optional port to connect to the remote server on; used if not
129
+ # using the standard SSH port, 22.
130
+ #
131
+ # Additionally, +keys+, +build+, +prebuild+, and +postbuild+ scripts may be
132
+ # specified to override the global scripts.
133
+ #
134
+ # == Script Substitution
135
+ #
136
+ # The +build+, +prebuild+, and +postbuild+ scripts require information from
137
+ # the Cartage and Cartage::Remote instances. When these scripts are rendered
138
+ # to disk, they will be run through Kernel#sprintf with string substitution
139
+ # parameters (<tt>%{<em>parameter-name</em>}</tt>). All of these values are
140
+ # computed from the local Cartage configuration.
141
+ #
142
+ # +repo_url+:: The repository URL.
143
+ # +name+:: The package name.
144
+ # +release_hashref+:: The release hashref to build.
145
+ # +timestamp+:: The build timestamp.
146
+ # +remote_address+:: The remote build host. Also available as +remote_host+
147
+ # for backwards compatability.
148
+ # +remote_port+:: The remote build host SSH port (may be empty).
149
+ # +remote_user+:: The remote build user.
150
+ # +build_root+:: The remote build root, (usually
151
+ # <tt>~<em>remote_user</em></tt>).
152
+ # +cartage_path+:: <tt><em>build_root</em>/cartage</tt>.
153
+ # +project_path+:: <tt><em>cartage_path</em>/<em>name</em></tt>.
154
+ # +isolation_path+:: <tt><em>project_path</em>/<em>timestamp</em></tt>.
155
+ # +build_path+:: The remote build path (contains the code to package).
156
+ # <tt><em>isolation_path</em>/<em>name</em></tt>
157
+ # +remote_bundle+:: A place where dependencies for the build can be installed
158
+ # locally. <tt><em>isolation_path</em>/deps</tt>. Typically
159
+ # used in the +build+ script.
160
+ # bundle install --path %{remote_bundle}
161
+ # +dependency_cache+:: The +dependency_cachevendor_cache+ for the remote
162
+ # server. Set the same as +project_path+.
163
+ # +config_file+:: The remote filename of the computed Cartage configuration.
164
+ # Must be provided to the remote run of +cartage+.
165
+ # bundle exec cartage build --config-file %{config_file}
166
+ # +build_script+:: The full path to the remote build script.
167
+ # <tt><em>isolation_path</em>/cartage-build-remote</tt>.
168
+ #
169
+ # == Configuration Example
170
+ #
171
+ # ---
172
+ # plugins:
173
+ # remote:
174
+ # hosts:
175
+ # default: build-server
176
+ # script: |
177
+ # #! /bin/bash
178
+ # bundle install --path %{remote_bundle} &&
179
+ # bundle exec cartage build --config-file %{config_file} &&
180
+ # bundle exec cartage s3 --config-file %{config_file}
181
+ class Remote < Cartage::Plugin
182
+ VERSION = '2.0.rc1' #:nodoc:
183
+
184
+ # Build on the remote server.
185
+ def build
186
+ stage.trigger! :local_setup
187
+ stage.trigger! :ssh_setup
188
+ stage.trigger! :run_prebuild
189
+ stage.trigger! :clone_remote
190
+ stage.trigger! :build_remote
191
+ stage.trigger! :clean_remote
192
+ stage.trigger! :complete
193
+ rescue Cartage::CLI::CustomExit
194
+ raise
195
+ rescue => e
196
+ error = e.exception("Remote error in stage #{stage.state}: #{e.message}")
197
+ error.set_backtrace(e.backtrace)
198
+ raise error
199
+ ensure
200
+ if postbuild_script
201
+ cartage.display 'Running postbuild script...'
202
+ system make_tmpscript('postbuild', postbuild_script, subs).path,
203
+ stage.state.to_s, error.to_s
204
+ end
205
+
206
+ tmpfiles.each do |tmpfile|
207
+ tmpfile.close
208
+ tmpfile.unlink
209
+ end
210
+ tmpfiles.clear
211
+ end
212
+
213
+ # Check that the configuration is correct. If +require_host+ is present, an
214
+ # exception will be thrown if a host is required and not present.
215
+ def check_config(require_host: false)
216
+ hosts = cartage.config(for_plugin: :remote).hosts
217
+ verify_hosts(hosts)
218
+ fail "No host #{name} present" if require_host && !hosts.dig(name)
219
+ end
220
+
221
+ private
222
+
223
+ def initialize(*) #:nodoc:
224
+ super
225
+ @host = @name = @keys = @key_data = nil
226
+
227
+ @stage = MicroMachine.new('new').tap { |stage|
228
+ stage.when :local_setup, 'new' => 'local_config'
229
+ stage.when :ssh_setup, 'local_config' => 'ssh_config'
230
+ stage.when :run_prebuild, 'ssh_config' => 'prebuild'
231
+ stage.when :clone_remote, 'prebuild' => 'remote_clone'
232
+ stage.when :build_remote, 'remote_clone' => 'remote_build'
233
+ stage.when :clean_remote, 'remote_build' => 'cleanup'
234
+ stage.when :complete, 'cleanup' => 'finished'
235
+ stage.on(:any) { |event| dispatch(event, stage.state) }
236
+ }
237
+ end
238
+
239
+ attr_reader :host
240
+ attr_reader :name
241
+ attr_reader :config
242
+ attr_reader :stage
243
+
244
+ attr_reader :build_root
245
+ attr_reader :config_file
246
+ attr_reader :key_data
247
+ attr_reader :keys
248
+ attr_reader :paths
249
+ attr_reader :subs
250
+
251
+ def prebuild_script
252
+ unless defined?(@prebuild_script)
253
+ @prebuild_script = host.prebuild || config.prebuild ||
254
+ DEFAULT_PREBUILD_SCRIPT
255
+ end
256
+ @prebuild_script
257
+ end
258
+
259
+ def postbuild_script
260
+ unless defined?(@postbuild_script)
261
+ @postbuild_script = host.postbuild || config.postbuild
262
+ end
263
+ @postbuild_script
264
+ end
265
+
266
+ def build_script
267
+ unless defined?(@build_script)
268
+ @build_script = host.build || config.build || DEFAULT_BUILD_SCRIPT
269
+ end
270
+ @build_script
271
+ end
272
+
273
+ def tmpfiles
274
+ @tmpfiles ||= []
275
+ end
276
+
277
+ def local_setup
278
+ @name = config.host || 'default'
279
+ host_config = config.dig(:hosts, name)
280
+ verify_host!(name, host_config)
281
+ @host = Cartage::Remote::Host.new(host_config)
282
+
283
+ fail ArgumentError, <<-exception if build_script.nil? || build_script.empty?
284
+ No build script to run on remote #{host}.
285
+ exception
286
+
287
+ # Force lazy values to be present during execution.
288
+ cartage.send(:realize!)
289
+
290
+ cartage.display 'Pre-build configuration...'
291
+ @paths = OpenStruct.new(build_root: build_root)
292
+ paths.cartage_path = paths.build_root.join('cartage')
293
+ paths.project_path = paths.cartage_path.join(cartage.name)
294
+ paths.isolation_path = paths.project_path.join(cartage.timestamp)
295
+ paths.build_path = paths.isolation_path.join(cartage.name)
296
+ paths.remote_bundle = paths.isolation_path.join('deps')
297
+ paths.dependency_cache = paths.project_path
298
+ paths.config_file = paths.isolation_path.join('cartage.yml')
299
+ paths.build_script = paths.isolation_path.join('cartage-build-remote')
300
+
301
+ @config_file = make_config(paths).path
302
+
303
+ @subs = OpenStruct.new(
304
+ paths.to_h.merge(
305
+ repo_url: cartage.repo_url,
306
+ name: cartage.name,
307
+ release_hashref: cartage.release_hashref,
308
+ timestamp: cartage.timestamp,
309
+ remote_host: host.address,
310
+ remote_address: host.address,
311
+ remote_port: host.port,
312
+ remote_user: host.user
313
+ )
314
+ )
315
+ end
316
+
317
+ def ssh_setup
318
+ host.configure_ssh(keys: keys, key_data: key_data)
319
+ end
320
+
321
+ def run_prebuild
322
+ cartage.display 'Running prebuild script...'
323
+ system(make_tmpscript('prebuild', prebuild_script, subs).path)
324
+ end
325
+
326
+ def clone_remote
327
+ cartage.display <<-message
328
+ Checking out #{cartage.repo_url} at #{cartage.release_hashref} remotely...
329
+ message
330
+
331
+ ssh "mkdir -p #{paths.isolation_path}"
332
+ host.scp.upload(config_file, user_path(paths.config_file))
333
+ ssh "git clone #{cartage.repo_url} #{paths.build_path}"
334
+ ssh <<-command
335
+ cd #{paths.build_path} && git checkout #{cartage.release_hashref}
336
+ command
337
+ end
338
+
339
+ def build_remote
340
+ cartage.display 'Running build script...'
341
+ script = make_tmpscript('build', build, subs).path
342
+ host.scp.upload(script, user_path(paths.build_script))
343
+ ssh "cd #{paths.build_path} && #{paths.build_script}"
344
+ end
345
+
346
+ def clean_remote
347
+ cartage.display 'Cleaning up after the build...'
348
+ ssh "rm -rf #{paths.isolation_path}"
349
+ end
350
+
351
+ def dispatch(event, state)
352
+ send(event) if respond_to?(event, true)
353
+ send(state) if respond_to?(state, true)
354
+ end
355
+
356
+ def resolve_plugin_config!(remote_config)
357
+ @config = remote_config
358
+ if config.dig(:server)
359
+ if config.dig(:hosts, :default)
360
+ fail ArgumentError,
361
+ 'Cannot configure both an implicit and explicit default host.'
362
+ end
363
+
364
+ config.hosts ||= OpenStruct.new
365
+ default = Cartage::Remote::Host.new(config.server).to_hash
366
+ config.hosts.default ||= OpenStruct.new(default)
367
+ end
368
+
369
+ if config.keys.kind_of?(OpenStruct)
370
+ @key_data = config.keys.to_h.values
371
+ else
372
+ @keys = Array(config.keys || '~/.ssh/*id_[rd]sa').flat_map { |key|
373
+ Pathname.glob(Pathname(key).expand_path)
374
+ }
375
+ end
376
+
377
+ @build_root = Pathname(config.build_root || '~')
378
+ @postbuild_script = config.postbuild
379
+ end
380
+
381
+ def ssh(*commands)
382
+ results = host.ssh.run(commands) do |stdout, stderr|
383
+ $stdout.print stdout unless stdout.nil?
384
+ $stderr.print stderr unless stderr.nil?
385
+ end
386
+
387
+ results.each do |result|
388
+ next if result.status.zero?
389
+
390
+ message = <<-msg
391
+ Remote error in stage #{stage.state}:
392
+ SSH command failed with status (#{result.status}):
393
+ #{result.command}
394
+ msg
395
+ fail Cartage::CLI::CustomExit.new(message, result.status)
396
+ end
397
+ end
398
+
399
+ def make_tmpfile(basename, content = nil)
400
+ Tempfile.new("#{basename}.").tap { |f|
401
+ f.write content || yield
402
+ f.close
403
+ tmpfiles << f
404
+ }
405
+ end
406
+
407
+ def make_tmpscript(basename, content, subs)
408
+ make_tmpfile(basename, content % subs.to_h).tap { |f|
409
+ File.chmod(0700, f.path)
410
+ }
411
+ end
412
+
413
+ def make_config(paths)
414
+ make_tmpfile('config.yml') do
415
+ config = Cartage::Config.new(cartage.config)
416
+ config.name = cartage.name
417
+ config.root_path = paths.build_path.to_s
418
+ config.timestamp = cartage.timestamp
419
+ config.release_hashref = cartage.release_hashref
420
+ config.compression = cartage.compression.to_s
421
+ config.disable_dependency_cache = cartage.disable_dependency_cache
422
+ config.dependency_cache_path = paths.dependency_cache.to_s
423
+ config.to_yaml
424
+ end
425
+ end
426
+
427
+ def user_path(path)
428
+ path.to_s.sub(%r{\A~/}, '')
429
+ end
430
+
431
+ def verify_hosts(hosts)
432
+ fail ArgumentError, 'No hosts present' if hosts.nil? || hosts.to_h.empty?
433
+
434
+ hosts.each_pair do |name, host|
435
+ verify_host(name, host)
436
+ end
437
+ end
438
+
439
+ def verify_host(name, host, &notify)
440
+ notify ||= ->(message) { warn message }
441
+
442
+ case host
443
+ when OpenStruct
444
+ host.dig(:address) || host.dig(:host)
445
+ when String
446
+ Cartage::Remote::Host::HOST_RE.match(host)[:address]
447
+ end || notify.("Host #{name} invalid: No host address present")
448
+ end
449
+
450
+ def verify_host!(name, host)
451
+ verify_host(name, host) { |message| fail ArgumentError, message }
452
+ end
453
+
454
+ DEFAULT_PREBUILD_SCRIPT = <<-script #:nodoc:
455
+ #!/bin/bash
456
+
457
+ ssh-keyscan -H %{remote_address} >> ~/.ssh/known_hosts
458
+ script
459
+
460
+ DEFAULT_BUILD_SCRIPT = <<-script #:nodoc:
461
+ #!/bin/bash
462
+
463
+ set -e
464
+
465
+ if [ -f Gemfile ]; then
466
+ bundle install --path %{remote_bundle}
467
+ bundle exec cartage build --config-file %{config_file} --target %{project_path}
468
+ else
469
+ cartage build --config-file %{config_file} --target %{project_path}
470
+ fi
471
+ script
472
+ end
473
+ end
474
+
475
+ require 'cartage/remote/host'