cartage-remote 1.1 → 2.0.rc1

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