flintlock 0.1.0
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.
- data/CHANGES.md +4 -0
- data/LICENSE +27 -0
- data/README.md +208 -0
- data/bin/flintlock +5 -0
- data/lib/flintlock/cli.rb +71 -0
- data/lib/flintlock/logger.rb +13 -0
- data/lib/flintlock/metadata.rb +53 -0
- data/lib/flintlock/module.rb +208 -0
- data/lib/flintlock/util.rb +27 -0
- data/lib/flintlock/version.rb +3 -0
- data/lib/flintlock.rb +3 -0
- metadata +89 -0
data/CHANGES.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2013, Jon McKenzie
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* The names of the contributors to this project may not be used to endorse
|
15
|
+
or promote products derived from this software without specific prior
|
16
|
+
written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
21
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
22
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
23
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
24
|
+
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
25
|
+
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
26
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27
|
+
POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
# flintlock
|
2
|
+
|
3
|
+
``flintlock`` is a simple application deployer inspired by Heroku's buildpacks.
|
4
|
+
|
5
|
+
At its core, it's a simple scripting API which allows developers/ops the ability
|
6
|
+
to create re-usable application deployments. In ``flintlock``, these deployments
|
7
|
+
are called "modules".
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
The latest release of ``flintlock`` will be published to ``rubygems.org``. To install,
|
12
|
+
just run:
|
13
|
+
|
14
|
+
```console
|
15
|
+
$ gem install flintlock
|
16
|
+
```
|
17
|
+
|
18
|
+
If you're running on a RHEL/CentOS 6 machine (or derivative), you might be able to
|
19
|
+
make use of the ``flintlock`` RPM spec file located in the git repository
|
20
|
+
(``flintlock.spec``) to build RPM packages. Assuming all of the dependencies are
|
21
|
+
installed, this should just be a matter of running:
|
22
|
+
|
23
|
+
```console
|
24
|
+
$ rpmbuild -ba flintlock.spec --define "scl ruby193"
|
25
|
+
```
|
26
|
+
|
27
|
+
## Tutorial
|
28
|
+
|
29
|
+
Let's deploy a sample ``redis`` module I've written. This tutorial assumes you
|
30
|
+
are running on a CentOS 6 machine with access to the EPEL package repository.
|
31
|
+
You'll also need ``git``.
|
32
|
+
|
33
|
+
After installing ``flintlock``, run the following:
|
34
|
+
|
35
|
+
```console
|
36
|
+
flintlock deploy git://github.com/jcmcken/flintlock-redis.git /some/empty/directory
|
37
|
+
```
|
38
|
+
|
39
|
+
In this case, the ``deploy`` command will recognize that you want to deploy from ``git``.
|
40
|
+
It will clone the remote repository, stage it, and then begin deploying the necessary
|
41
|
+
files and directories to ``/some/empty/directory``. Let's see what happens:
|
42
|
+
|
43
|
+
|
44
|
+
```console
|
45
|
+
$ flintlock deploy git://github.com/jcmcken/flintlock-redis.git /some/empty/directory
|
46
|
+
run fetching module
|
47
|
+
info deploying jcmcken/redis (0.0.1) to '/some/empty/directory'
|
48
|
+
create creating deploy directory
|
49
|
+
run installing and configuring dependencies
|
50
|
+
create staging application files
|
51
|
+
run launching the application
|
52
|
+
run altering application runtime environment
|
53
|
+
info complete!
|
54
|
+
$
|
55
|
+
```
|
56
|
+
|
57
|
+
Assuming the module was written well enough, these messages should indicate that our
|
58
|
+
``redis`` server is running. Let's verify:
|
59
|
+
|
60
|
+
```console
|
61
|
+
$ ps -ef | grep redis
|
62
|
+
jcmcken 24846 1 0 17:41 ? 00:00:00 /usr/sbin/redis-server /some/empty/directory/etc/redis.conf
|
63
|
+
jcmcken 24865 19343 0 17:41 pts/1 00:00:00 grep redis
|
64
|
+
```
|
65
|
+
|
66
|
+
Let's take a look at the deploy directory, ``/some/empty/directory``:
|
67
|
+
|
68
|
+
```console
|
69
|
+
$ tree /some/empty/directory
|
70
|
+
/some/empty/directory
|
71
|
+
|-- bin
|
72
|
+
| `-- redis
|
73
|
+
|-- data
|
74
|
+
|-- etc
|
75
|
+
| `-- redis.conf
|
76
|
+
|-- log
|
77
|
+
| |-- redis.log
|
78
|
+
| |-- stderr.log
|
79
|
+
| `-- stdout.log
|
80
|
+
`-- run
|
81
|
+
`-- redis.pid
|
82
|
+
```
|
83
|
+
|
84
|
+
You'll notice that everything for this ``redis`` server is self-contained within our deploy
|
85
|
+
directory. This is a central tenet of ``flintlock``:
|
86
|
+
|
87
|
+
**An application deployment is always self-contained within a single directory**
|
88
|
+
|
89
|
+
How well an application adheres to this philosophy depends on the application. For instance,
|
90
|
+
some applications may not have configurable ``/tmp`` directories. For transient data, this
|
91
|
+
is usually acceptable. But all of the important files should really be located together.
|
92
|
+
|
93
|
+
## Supported Formats
|
94
|
+
|
95
|
+
Currently ``flintlock`` can install modules from a number of sources. In addition to
|
96
|
+
local directories, ``flintlock`` supports the following protocols/formats:
|
97
|
+
|
98
|
+
* ``git``
|
99
|
+
* ``tar`` or ``tar.gz`` over ``http``/``https``
|
100
|
+
|
101
|
+
Attempting to install any other way will throw an error message similar to the following:
|
102
|
+
|
103
|
+
```console
|
104
|
+
run fetching module
|
105
|
+
error don't know how to download 'https://github.com'!
|
106
|
+
```
|
107
|
+
|
108
|
+
## Writing a Module
|
109
|
+
|
110
|
+
### Introduction
|
111
|
+
|
112
|
+
``flintlock`` modules are simply a bunch of scripts which follow a certain convention.
|
113
|
+
|
114
|
+
At its heart, a module has the following minimal layout:
|
115
|
+
|
116
|
+
```text
|
117
|
+
sample-app-1
|
118
|
+
|-- bin
|
119
|
+
| |-- defaults
|
120
|
+
| |-- modify
|
121
|
+
| |-- prepare
|
122
|
+
| |-- stage
|
123
|
+
| |-- start
|
124
|
+
| `-- stop
|
125
|
+
`-- metadata.json
|
126
|
+
```
|
127
|
+
|
128
|
+
Running ``flintlock new`` in an empty directory of your choosing will automatically
|
129
|
+
generate this structure.
|
130
|
+
|
131
|
+
The top-level directory (in this case, ``sample-app-1``) can be called anything.
|
132
|
+
|
133
|
+
The files under ``bin`` are executable scripts (using any language you care to use). All
|
134
|
+
of these scripts must exist, but they need not do anything. More on these later.
|
135
|
+
|
136
|
+
The ``metadata.json`` file contains metadata about the module. This metadata looks as follows:
|
137
|
+
|
138
|
+
```json
|
139
|
+
{
|
140
|
+
"author": "jcmcken",
|
141
|
+
"name": "sample-app-1",
|
142
|
+
"version": "0.0.1"
|
143
|
+
}
|
144
|
+
```
|
145
|
+
|
146
|
+
All three keys (``author``, ``name``, ``version``) are required, but can be any value. These
|
147
|
+
metadata are merely used to namespace the module.
|
148
|
+
|
149
|
+
``flintlock`` developers can choose to include more files in their modules if needed.
|
150
|
+
|
151
|
+
### Stages
|
152
|
+
|
153
|
+
``flintlock`` has different "stages" of execution that occur in a specific order every time
|
154
|
+
you run a deployment.
|
155
|
+
|
156
|
+
These stages correspond directly to the scripts under ``bin/``.
|
157
|
+
|
158
|
+
The most important stages, and their purpose, are as follows. (The stages occur in the order listed below)
|
159
|
+
|
160
|
+
* ``prepare``: Install or compile any required dependencies. This script takes no arguments.
|
161
|
+
* ``stage``: Stage the application directories and files. This script takes a single argument,
|
162
|
+
which is the directory where your app will be deployed. This directory need not exist, but if
|
163
|
+
it does, it must be empty.
|
164
|
+
* ``start``: Start the application. This script takes the same argument passed to ``stage``.
|
165
|
+
* ``modify``: Once the application is started, perform some runtime modifications. For instance,
|
166
|
+
if you've just started a MySQL server, you may want to remove the default tables or add a
|
167
|
+
password to the database superuser. This script takes the same argument passed to ``stage``
|
168
|
+
and ``start``.
|
169
|
+
|
170
|
+
The API between these scripts and ``flintlock`` is as follows:
|
171
|
+
|
172
|
+
* If the script exits with a return code of ``0``, ``flintlock`` will think that the script
|
173
|
+
succeeded.
|
174
|
+
* If the script exits with a return code of ``1``, ``flintlock`` will think that the script
|
175
|
+
has failed.
|
176
|
+
* Any other return code, and ``flintlock`` will think that some sort of internal error has
|
177
|
+
occurred. In other words, something outside of the script's control failed.
|
178
|
+
|
179
|
+
When ``flintlock`` encounters a non-zero exit code, it will halt execution and display an
|
180
|
+
error.
|
181
|
+
|
182
|
+
### Configuration Defaults
|
183
|
+
|
184
|
+
A ``flintlock`` module may also choose to utilize the ``bin/defaults`` script to set
|
185
|
+
configuration defaults.
|
186
|
+
|
187
|
+
By default, ``flintlock`` will source this script prior to executing any of the other
|
188
|
+
stages.
|
189
|
+
|
190
|
+
A user can choose to override these defaults at the command line. For example, if your
|
191
|
+
``defaults`` script looks like:
|
192
|
+
|
193
|
+
```bash
|
194
|
+
PORT=80
|
195
|
+
```
|
196
|
+
|
197
|
+
The user can override this at the command line by running:
|
198
|
+
|
199
|
+
```console
|
200
|
+
PORT=8080 flintlock deploy <module> <deploy_dir>
|
201
|
+
```
|
202
|
+
|
203
|
+
``flintlock`` will transparently override the default ``PORT`` with the env var passed at the
|
204
|
+
command line.
|
205
|
+
|
206
|
+
### Examples
|
207
|
+
|
208
|
+
An example ``flintlock`` module can be found @ http://github.com/jcmcken/flintlock-redis.git.
|
data/bin/flintlock
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'flintlock/module'
|
3
|
+
|
4
|
+
module Flintlock
|
5
|
+
class Cli < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
desc "deploy MODULE DIRECTORY", "Deploy a flintlock module MODULE to DIRECTORY"
|
9
|
+
method_option :debug, :type => :boolean, :description => "enable debug output", :default => false
|
10
|
+
def deploy(uri, app_dir)
|
11
|
+
say_status "run", "fetching module", :magenta
|
12
|
+
mod = get_module(uri, options)
|
13
|
+
say_status "info", "deploying #{mod.full_name} to '#{app_dir}'", :blue
|
14
|
+
say_status "create", "creating deploy directory"
|
15
|
+
mod.create_app_dir(app_dir) rescue abort("deploy directory is not empty")
|
16
|
+
|
17
|
+
begin
|
18
|
+
say_status "run", "installing and configuring dependencies", :magenta
|
19
|
+
mod.prepare
|
20
|
+
say_status "create", "staging application files"
|
21
|
+
mod.stage(app_dir)
|
22
|
+
say_status "run", "launching the application", :magenta
|
23
|
+
mod.start(app_dir)
|
24
|
+
say_status "run", "altering application runtime environment", :magenta
|
25
|
+
mod.modify(app_dir)
|
26
|
+
say_status "info", "complete!", :blue
|
27
|
+
rescue Errno::EACCES => e
|
28
|
+
abort("#{e.message.gsub(/Permission denied/, 'permission denied')}")
|
29
|
+
rescue RunFailure
|
30
|
+
abort('stage failed!')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "new [DIRECTORY]", "Generate a new, minimal flintlock module"
|
35
|
+
def new(directory = Dir.pwd)
|
36
|
+
abort("directory isn't empty!") if ! Util.empty_directory?(directory)
|
37
|
+
inside(directory) do
|
38
|
+
empty_directory "bin"
|
39
|
+
inside("bin") do
|
40
|
+
Module.script_names.each do |script|
|
41
|
+
create_file script
|
42
|
+
end
|
43
|
+
end
|
44
|
+
create_file(Metadata.filename, Metadata.empty)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get_module(uri, options={})
|
51
|
+
begin
|
52
|
+
Flintlock::Module.new(uri, options)
|
53
|
+
rescue InvalidModule => e
|
54
|
+
abort("invalid flintlock module '#{e}'")
|
55
|
+
rescue UnsupportedModuleURI => e
|
56
|
+
abort("don't know how to download '#{e}'!")
|
57
|
+
rescue ModuleDownloadError => e
|
58
|
+
abort("failed to download '#{e}'")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def error(message)
|
63
|
+
say_status "error", message, :red
|
64
|
+
end
|
65
|
+
|
66
|
+
def abort(message)
|
67
|
+
error(message)
|
68
|
+
exit(1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Flintlock
|
4
|
+
class Metadata
|
5
|
+
attr_reader :filename
|
6
|
+
|
7
|
+
def initialize(filename = nil)
|
8
|
+
@filename = filename || default_metadata_file
|
9
|
+
@data = Metadata.load(@filename)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.filename
|
13
|
+
'metadata.json'
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?
|
17
|
+
begin
|
18
|
+
result = ! [author, version, name].map(&:empty?).any?
|
19
|
+
rescue
|
20
|
+
result = false
|
21
|
+
end
|
22
|
+
return result
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_metadata_file
|
26
|
+
File.join(Dir.pwd, Metadata.filename)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.load(filename)
|
30
|
+
JSON.load(File.read(filename))
|
31
|
+
end
|
32
|
+
|
33
|
+
def author
|
34
|
+
@data.fetch('author')
|
35
|
+
end
|
36
|
+
|
37
|
+
def version
|
38
|
+
@data.fetch('version')
|
39
|
+
end
|
40
|
+
|
41
|
+
def name
|
42
|
+
@data.fetch('name')
|
43
|
+
end
|
44
|
+
|
45
|
+
def full_name
|
46
|
+
"#{author}/#{name} (#{version})"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.empty
|
50
|
+
{"author" => "", "version" => "", "name" => ""}.to_json
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'flintlock/metadata'
|
2
|
+
require 'flintlock/logger'
|
3
|
+
require 'flintlock/util'
|
4
|
+
require 'open3'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'logger'
|
7
|
+
require 'shellwords'
|
8
|
+
require 'uri'
|
9
|
+
require 'tmpdir'
|
10
|
+
require 'tempfile'
|
11
|
+
require 'open-uri'
|
12
|
+
|
13
|
+
module Flintlock
|
14
|
+
class InvalidModule < RuntimeError; end
|
15
|
+
class UnsupportedModuleURI < RuntimeError; end
|
16
|
+
class ModuleDownloadError < RuntimeError; end
|
17
|
+
class RunFailure < RuntimeError; end
|
18
|
+
|
19
|
+
class Module
|
20
|
+
attr_reader :uri, :metadata
|
21
|
+
|
22
|
+
def initialize(uri = nil, options={})
|
23
|
+
# track temporary files and directories for deletion
|
24
|
+
@tmpfiles = []
|
25
|
+
|
26
|
+
# destroy tmp files on exit
|
27
|
+
at_exit { handle_exit }
|
28
|
+
|
29
|
+
@debug = !!options[:debug]
|
30
|
+
@uri = uri || Dir.pwd
|
31
|
+
@log = load_logger
|
32
|
+
@root_dir = download_from_uri(@uri)
|
33
|
+
@metadata = load_metadata
|
34
|
+
|
35
|
+
load_scripts!
|
36
|
+
validate
|
37
|
+
|
38
|
+
@env = load_env(@defaults_script)
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def download_from_uri(uri)
|
43
|
+
case URI.parse(uri).scheme
|
44
|
+
when nil # no scheme == local file
|
45
|
+
uri
|
46
|
+
when 'git'
|
47
|
+
handle_git_uri(uri)
|
48
|
+
when 'http', 'https'
|
49
|
+
raise UnsupportedModuleURI.new(uri) if ! Util.supported_archive?(uri)
|
50
|
+
# over these protocols, we're getting an archive
|
51
|
+
handle_archive(handle_http_uri(uri))
|
52
|
+
else
|
53
|
+
raise UnsupportedModuleURI, uri
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_exit
|
58
|
+
@tmpfiles.each { |x| FileUtils.rm_rf(x, :secure => true) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_git_uri(uri)
|
62
|
+
root_dir = Dir.mktmpdir
|
63
|
+
@tmpfiles << root_dir
|
64
|
+
command = Shellwords.join(['git', 'clone', uri, root_dir])
|
65
|
+
stdout, stderr, status = Open3.capture3(command)
|
66
|
+
raise ModuleDownloadError, uri if status.exitstatus != 0
|
67
|
+
root_dir
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_http_uri(uri, buffer=8192)
|
71
|
+
tmpfile = Tempfile.new(['flintlock', Util.full_extname(uri)]).path
|
72
|
+
@tmpfiles << tmpfile
|
73
|
+
open(uri) do |input|
|
74
|
+
open(tmpfile, 'wb') do |output|
|
75
|
+
while ( buf = input.read(buffer))
|
76
|
+
output.write buf
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
tmpfile
|
81
|
+
rescue OpenURI::HTTPError
|
82
|
+
raise ModuleDownloadError, uri
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_archive(filename)
|
86
|
+
tmpdir = Dir.mktmpdir
|
87
|
+
@tmpfiles << tmpdir
|
88
|
+
case filename
|
89
|
+
when /\.tar\.gz$/
|
90
|
+
command = ['tar', 'xfz', filename, '-C', tmpdir]
|
91
|
+
when /\.tar$/
|
92
|
+
command = ['tar', 'xf', filename, '-C', tmpdir]
|
93
|
+
else
|
94
|
+
raise UnsupportedModuleURI, filename
|
95
|
+
end
|
96
|
+
_, _, status = Open3.capture3(Shellwords.join(command))
|
97
|
+
raise ModuleDownloadError if status.exitstatus != 0
|
98
|
+
tmpdir
|
99
|
+
end
|
100
|
+
|
101
|
+
def full_name
|
102
|
+
@metadata.full_name
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.script_names
|
106
|
+
['defaults', 'modify', 'prepare', 'stage', 'start', 'stop']
|
107
|
+
end
|
108
|
+
|
109
|
+
def scripts
|
110
|
+
[@modify_script, @prepare_script, @stage_script, @start_script, @stop_script, @defaults_script]
|
111
|
+
end
|
112
|
+
|
113
|
+
def scripts_exist?
|
114
|
+
scripts.map { |x| File.file?(x) }.all?
|
115
|
+
end
|
116
|
+
|
117
|
+
def valid?
|
118
|
+
@metadata.valid? && scripts_exist?
|
119
|
+
end
|
120
|
+
|
121
|
+
def prepare
|
122
|
+
@log.info("running prepare stage: #{@prepare_script}")
|
123
|
+
run_script(@prepare_script)
|
124
|
+
end
|
125
|
+
|
126
|
+
def stage(app_dir)
|
127
|
+
@log.info("running stage stage: #{@stage_script}")
|
128
|
+
run_script(@stage_script, app_dir)
|
129
|
+
end
|
130
|
+
|
131
|
+
def modify(app_dir)
|
132
|
+
@log.info("running modify stage: #{@modify_script}")
|
133
|
+
run_script(@modify_script, app_dir)
|
134
|
+
end
|
135
|
+
|
136
|
+
def start(app_dir)
|
137
|
+
@log.info("running start stage: #{@start_script}")
|
138
|
+
run_script(@start_script, app_dir)
|
139
|
+
end
|
140
|
+
|
141
|
+
def stop(app_dir)
|
142
|
+
@log.info("running stop stage: #{@stop_script}")
|
143
|
+
run_script(@stop_script, app_dir)
|
144
|
+
end
|
145
|
+
|
146
|
+
def current_env
|
147
|
+
Hash[ENV.to_a] # get rid of ENV obj
|
148
|
+
end
|
149
|
+
|
150
|
+
def load_env(defaults_script)
|
151
|
+
# hokey, but seems to work
|
152
|
+
env_data = %x{set -a && source #{defaults_script} && env}.split.map{ |x| x.split('=', 2) }
|
153
|
+
env = Hash[env_data]
|
154
|
+
@log.debug("defaults script is #{defaults_script}")
|
155
|
+
@log.debug("defaults env is #{env.inspect}")
|
156
|
+
env = env.merge(current_env)
|
157
|
+
@log.debug("merged env is #{env.inspect}")
|
158
|
+
env
|
159
|
+
end
|
160
|
+
|
161
|
+
def create_app_dir(app_dir)
|
162
|
+
FileUtils.mkdir_p(app_dir)
|
163
|
+
raise if ! Util.empty_directory?(app_dir)
|
164
|
+
end
|
165
|
+
|
166
|
+
private
|
167
|
+
|
168
|
+
def load_scripts!
|
169
|
+
Module.script_names.map do |x|
|
170
|
+
instance_variable_set("@#{x}_script".to_sym, File.join(@root_dir, 'bin', x))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def validate
|
175
|
+
raise InvalidModule.new(@uri) if ! valid?
|
176
|
+
end
|
177
|
+
|
178
|
+
def load_logger
|
179
|
+
log = Logger.new(STDOUT)
|
180
|
+
log.silence! if ! @debug
|
181
|
+
log
|
182
|
+
end
|
183
|
+
|
184
|
+
def load_metadata
|
185
|
+
begin
|
186
|
+
Metadata.new(File.join(@root_dir, Metadata.filename))
|
187
|
+
rescue Errno::ENOENT
|
188
|
+
raise InvalidModule, uri
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def run(command)
|
193
|
+
handle_run(*Open3.capture3(@env, command))
|
194
|
+
end
|
195
|
+
|
196
|
+
def run_script(script, *args)
|
197
|
+
run(Shellwords.join([script, *args]))
|
198
|
+
end
|
199
|
+
|
200
|
+
def handle_run(stdout, stderr, status)
|
201
|
+
stdout.lines.each { |x| @log.info(x) }
|
202
|
+
if status.exitstatus != 0
|
203
|
+
stderr.lines.each { |x| @log.error(x) }
|
204
|
+
raise RunFailure
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Flintlock
|
2
|
+
class Util
|
3
|
+
def self.empty_directory?(directory)
|
4
|
+
Dir[File.join(directory, '*')].empty?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.supported_archives
|
8
|
+
['.tar.gz', '.tar']
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.supported_archive?(filename)
|
12
|
+
Util.supported_archives.include?(full_extname(filename))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.full_extname(filename)
|
16
|
+
data = []
|
17
|
+
current_filename = filename.dup
|
18
|
+
while true
|
19
|
+
ext = File.extname(current_filename)
|
20
|
+
break if ext.empty?
|
21
|
+
current_filename = current_filename.gsub(/#{ext}$/, '')
|
22
|
+
data << ext
|
23
|
+
end
|
24
|
+
data.reverse.join
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/flintlock.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: flintlock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jon McKenzie
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: A simple application deployer inspired by Heroku's buildpacks
|
47
|
+
email: jcmcken@gmail.com
|
48
|
+
executables:
|
49
|
+
- flintlock
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/flintlock.rb
|
54
|
+
- lib/flintlock/module.rb
|
55
|
+
- lib/flintlock/metadata.rb
|
56
|
+
- lib/flintlock/util.rb
|
57
|
+
- lib/flintlock/cli.rb
|
58
|
+
- lib/flintlock/logger.rb
|
59
|
+
- lib/flintlock/version.rb
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- CHANGES.md
|
63
|
+
- bin/flintlock
|
64
|
+
homepage: https://github.com/jcmcken/flintlock
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.9.3
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.8.23
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: A simple application deployer
|
89
|
+
test_files: []
|