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 +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'
|