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 +4 -4
- data/Contributing.md +68 -0
- data/{History.rdoc → History.md} +11 -0
- data/{Licence.rdoc → Licence.md} +5 -5
- data/Manifest.txt +8 -9
- data/README.rdoc +29 -10
- data/Rakefile +47 -30
- data/lib/cartage/commands/remote.rb +48 -0
- data/lib/cartage/plugins/remote.rb +475 -0
- data/lib/cartage/remote/host.rb +101 -0
- data/test/minitest_config.rb +11 -0
- data/test/test_cartage_remote.rb +110 -0
- metadata +74 -35
- data/.autotest +0 -27
- data/.gemtest +0 -1
- data/.minitest.rb +0 -2
- data/Contributing.rdoc +0 -63
- data/Gemfile +0 -9
- data/lib/cartage/remote.rb +0 -419
- data/lib/cartage/remote/command.rb +0 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a407715cfc69a80ee22680f2bd2596987503a27f
|
4
|
+
data.tar.gz: aa30045f89581ac22feb52783e326cbfb43c26d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 790d60ff41276adef303b1b3dac7374a688db17cb3c7db93b32dafcfbb775c3410c8bb41c097ba133ce4baacbf552c2d37f4b7957ceb90756844c1062da2257d
|
7
|
+
data.tar.gz: 3db9359ef4e88f7fe29e999009c72364518c38671eb4fa55e0dc85505d97cfce33ea1018efc13b31ae9ad04c55a278259c8de2956f0b6ffccf512e22e1b1cb57
|
data/Contributing.md
ADDED
@@ -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
|
data/{History.rdoc → History.md}
RENAMED
@@ -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
|
data/{Licence.rdoc → Licence.md}
RENAMED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
## Licence
|
2
2
|
|
3
3
|
This software is available under an MIT-style licence.
|
4
4
|
|
5
|
-
*
|
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
|
-
*
|
15
|
-
|
16
|
-
|
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.
|
data/Manifest.txt
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
.
|
2
|
-
.
|
3
|
-
.
|
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
|
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
|
data/README.rdoc
CHANGED
@@ -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.
|
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.
|
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', '~>
|
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
|
41
|
-
compatibility.
|
55
|
+
cartage-remote will generally track cartage for major versions to ensure plugin
|
56
|
+
API compatibility.
|
42
57
|
|
43
|
-
|
58
|
+
== Community and Contributing
|
44
59
|
|
45
|
-
|
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
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'hoe'
|
5
|
-
require '
|
5
|
+
require 'rake/clean'
|
6
6
|
|
7
7
|
Hoe.plugin :doofus
|
8
|
-
Hoe.plugin :email unless ENV['CI']
|
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.
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
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, ¬ify)
|
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'
|