flintlock 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|