pawnee 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +158 -0
- data/Rakefile +11 -0
- data/bin/pawnee +10 -0
- data/docs/FAQ.md +33 -0
- data/docs/GUIDE.md +102 -0
- data/docs/TODO.md +10 -0
- data/lib/pawnee/pawnee/actions/base_model.rb +35 -0
- data/lib/pawnee/pawnee/actions/compile.rb +140 -0
- data/lib/pawnee/pawnee/actions/package.rb +111 -0
- data/lib/pawnee/pawnee/actions/user.rb +116 -0
- data/lib/pawnee/pawnee/actions.rb +29 -0
- data/lib/pawnee/pawnee/base.rb +265 -0
- data/lib/pawnee/pawnee/cli.rb +72 -0
- data/lib/pawnee/pawnee/invocation.rb +13 -0
- data/lib/pawnee/pawnee/parser/options.rb +55 -0
- data/lib/pawnee/pawnee/railtie.rb +23 -0
- data/lib/pawnee/pawnee/roles.rb +79 -0
- data/lib/pawnee/pawnee/setup.rb +23 -0
- data/lib/pawnee/pawnee/templates/newgem/Gemfile.tt +4 -0
- data/lib/pawnee/pawnee/templates/newgem/LICENSE.tt +22 -0
- data/lib/pawnee/pawnee/templates/newgem/README.md.tt +29 -0
- data/lib/pawnee/pawnee/templates/newgem/Rakefile.tt +2 -0
- data/lib/pawnee/pawnee/templates/newgem/bin/newgem.tt +3 -0
- data/lib/pawnee/pawnee/templates/newgem/gitignore.tt +17 -0
- data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem/base.rb.tt +27 -0
- data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem/version.rb.tt +7 -0
- data/lib/pawnee/pawnee/templates/newgem/lib/pawnee/newgem.rb.tt +10 -0
- data/lib/pawnee/pawnee/templates/newgem/newgem.gemspec.tt +18 -0
- data/lib/pawnee/pawnee/version.rb +3 -0
- data/lib/pawnee/pawnee.rb +7 -0
- data/pawnee.gemspec +31 -0
- data/spec/actions/compile_spec.rb +26 -0
- data/spec/actions/package_spec.rb +41 -0
- data/spec/actions/user_spec.rb +54 -0
- data/spec/base_spec.rb +167 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/vagrant/.vagrant +1 -0
- data/spec/vagrant/Vagrantfile +99 -0
- data/spec/vagrant/vagrant.rb +30 -0
- metadata +328 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Ryan Stout
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# Pawnee - making server provisioning easier
|
2
|
+
|
3
|
+
[NOTE: This is still a work in progress and not ready for use yet]
|
4
|
+
|
5
|
+
## Why
|
6
|
+
|
7
|
+
You may wonder why do we need another provisioning system, [here's](https://github.com/ryanstout/pawnee/blob/master/docs/FAQ.md) my answer.
|
8
|
+
|
9
|
+
## Goals
|
10
|
+
|
11
|
+
This system will:
|
12
|
+
|
13
|
+
1. **Setup recipes as gems**
|
14
|
+
- Gems get prefixed so you can search for them
|
15
|
+
- Gems inherit from main gem
|
16
|
+
2. **Gems recipes can be overridden in a rails folder**
|
17
|
+
- either by a load path solution or monkeypatching
|
18
|
+
- they have a classname that can be predicted from the gem name
|
19
|
+
- so anything that overrides that class can monkeypatch or replace parts of it
|
20
|
+
3. **It should use bundler**
|
21
|
+
- so you just list the things you need on your server in a certain group (that only gets run on when installing)
|
22
|
+
- you could use :path => '...'
|
23
|
+
- the gems don't install the things in a bundle, just provide a task you can run to set them up
|
24
|
+
4. **It should run gems locally or remote**
|
25
|
+
- based on the --servers option passed in
|
26
|
+
5. **It should integrate with capistrano**
|
27
|
+
- we need a place to store server info....
|
28
|
+
- redis somewhere?
|
29
|
+
- Serverfile/Serverfile.lock
|
30
|
+
- the servers and roles should be maintained by this system
|
31
|
+
- but deploying should be a separate thing (not like rubber)
|
32
|
+
6. **It should use thor for template stuff**
|
33
|
+
- and just overwrite it so files get copied to remote destinations if needed
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
## Installation
|
38
|
+
|
39
|
+
Add any pawnee gem's you want to use to bundler:
|
40
|
+
|
41
|
+
gem 'pawnee-nginx'
|
42
|
+
|
43
|
+
You can see currently available recipe gems [here](https://rubygems.org/search?utf8=%E2%9C%93&query=pawnee)
|
44
|
+
|
45
|
+
[Or you can create your own recipy gems](https://github.com/ryanstout/pawnee/blob/master/docs/GUIDE.md)
|
46
|
+
|
47
|
+
## Usage
|
48
|
+
|
49
|
+
### Config File
|
50
|
+
|
51
|
+
Setup a config/pawnee.yml file. In this file you can specify any options you want to pass along with the servers:
|
52
|
+
|
53
|
+
Here's an example config with server's:
|
54
|
+
|
55
|
+
servers:
|
56
|
+
- domain: server1.com
|
57
|
+
roles: [nginx, unicorn]
|
58
|
+
- domain: server2.com
|
59
|
+
roles: [unicorn]
|
60
|
+
|
61
|
+
Then run:
|
62
|
+
|
63
|
+
bundle exec pawnee setup
|
64
|
+
|
65
|
+
## Contributing
|
66
|
+
|
67
|
+
1. Fork it
|
68
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
69
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
70
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
5. Create new Pull Request
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
## Pawnee Provides Helpers For:
|
77
|
+
|
78
|
+
- files
|
79
|
+
- directories
|
80
|
+
- packages
|
81
|
+
- service calls (via invoking methods in other recipes)
|
82
|
+
- compile (untar/make/make install)
|
83
|
+
- users
|
84
|
+
- cron? [not complete] (maybe leverage whenever gem)
|
85
|
+
- env [not complete] (manage adding things to .base_profile somehow - maybe leverage insert_into_file stuff?)
|
86
|
+
|
87
|
+
## Standard Tasks
|
88
|
+
|
89
|
+
Pawnee defined some convention for task names so all gem's can provide standard ways to call things
|
90
|
+
|
91
|
+
- #setup
|
92
|
+
- #teardown
|
93
|
+
- #restart
|
94
|
+
- #stop
|
95
|
+
- #start
|
96
|
+
|
97
|
+
|
98
|
+
## Options
|
99
|
+
|
100
|
+
In a task, recipes can access and change the self.options hash. These values will be
|
101
|
+
passed from one task to another. Gem tasks will be executed in the order they are defined
|
102
|
+
in bundler (at the moment, this may change)
|
103
|
+
|
104
|
+
## Configuration
|
105
|
+
|
106
|
+
standard config options:
|
107
|
+
- servers
|
108
|
+
- git_repo_url
|
109
|
+
- web_root
|
110
|
+
- aws...
|
111
|
+
- s3...
|
112
|
+
|
113
|
+
## exposed by unicorn for example
|
114
|
+
app_server_locations ['localhost:3000', 'localhost:3001'] - gets picked up on by nginx maybe?
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
### ThorSsh Extensions to Thor
|
119
|
+
We make a few modifications to allow it to be used as the base to pawnee
|
120
|
+
1) We make it so all actions can be run either locally or remotely via ssh through the thor-ssh gem
|
121
|
+
2) We make options mutable and use that to allow the passing around of options
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
server connection system
|
127
|
+
- fog
|
128
|
+
- looks at fog api and tags to figure out what servers are running and their roles
|
129
|
+
|
130
|
+
core gem
|
131
|
+
- provides dsl features for setting stuff up
|
132
|
+
- packages ['...', '...'] # uses apt
|
133
|
+
- file copy in with erb
|
134
|
+
- all DSL commands can be run either locally or remote
|
135
|
+
- so file copy uploads if needed
|
136
|
+
- provides setup for defaults
|
137
|
+
- ec2/s3 credentials
|
138
|
+
- deploy_path(/mnt/[name])
|
139
|
+
- provides generator for building new gems
|
140
|
+
|
141
|
+
deployment:
|
142
|
+
- include in deploy.rb to setup servers and roles
|
143
|
+
- standard cap deploy
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
### Roles
|
150
|
+
- gems define a roles they provide
|
151
|
+
- [logically, this can only be one role right?]
|
152
|
+
- multiple gems can provide the same role, but one gem can not provide multiple
|
153
|
+
- you say which roles you want to run (as --roles)
|
154
|
+
- servers say which roles they provide
|
155
|
+
|
156
|
+
1) list of gems that run the listed roles
|
157
|
+
2) run on each server that provides
|
158
|
+
|
data/Rakefile
ADDED
data/bin/pawnee
ADDED
data/docs/FAQ.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Why is this better than Chef/Puppet?
|
2
|
+
|
3
|
+
Thats a tough question. I try to avoid reinventing the wheel, but it seems like the current wheel has some problems.
|
4
|
+
|
5
|
+
The main reasons is that Chef and Puppet are both huge solutions to a problem I think could be simplified with a few constraints and better choices. (chef is over 110k lines of ruby, it doesn't feel like this is a 110k lines of ruby problem)
|
6
|
+
|
7
|
+
Also, both seem to have business models that actually harm the product. For example with Chef, they charge you for a product to help set everything up. That means that they have incentives to make it difficult to setup.
|
8
|
+
|
9
|
+
Here's a few reason why Pawnee is better:
|
10
|
+
|
11
|
+
1) Simpler, well written, documented code
|
12
|
+
2) Leverages existing technology people know (rubygems, thor)
|
13
|
+
3) Code reuse is built in (all recipes are gems, any part of the gem can be overridden without changing the gem - like rails engines)
|
14
|
+
4) No need to bootstrap ruby - All actions can be run either locally (with a local ruby) or from a remote server over ssh
|
15
|
+
5) Clean logging (yea, this is important)
|
16
|
+
6) Tests - everything is tested using Vagrant
|
17
|
+
|
18
|
+
|
19
|
+
Also, just for good measure, here's a few reasons why its worse:
|
20
|
+
|
21
|
+
1) Only supports ubuntu (currently - the plan is to get the kinks worked out on ubuntu first)
|
22
|
+
2) Fewer recipes (again, currently)
|
23
|
+
|
24
|
+
|
25
|
+
## Problems with Chef/Puppet
|
26
|
+
|
27
|
+
[Note: this is my opinion]
|
28
|
+
|
29
|
+
1. Little to no testing of recipes (at least for Chef)
|
30
|
+
2. Complicated recipe upgrading/code reuse patterns
|
31
|
+
3. You need to run the code locally (should have option to run over ssh)
|
32
|
+
|
33
|
+
[chef's providers as an example: http://wiki.opscode.com/display/chef/Resources]
|
data/docs/GUIDE.md
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# Recipe Gem Creation Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Pawnee tries to make it very easy to create recipes for setting up things on remote or local servers. Pawnee provides a gem generator to get started, simply run:
|
6
|
+
|
7
|
+
pawnee gem [gemname]
|
8
|
+
|
9
|
+
This will generate files for a gem in the current directory. The word Recipe is used to refer to these pawnee gems (since it is a standard term in the provisioning world.)
|
10
|
+
|
11
|
+
## base.rb
|
12
|
+
|
13
|
+
The main code for your gem will go in lib/pawnee/[gemname]/base.rb This file is a recipe class that inherits from Pawnee::Base.
|
14
|
+
|
15
|
+
Pawnee uses the [thor gem](https://github.com/wycats/thor) to provide things like tasks and actions. Pawnee::Base extends thor and adds in things to do the following:
|
16
|
+
|
17
|
+
1. allow actions to run remotely over ssh or locally.
|
18
|
+
2. allow tasks to run multiple times (once per server).
|
19
|
+
3. allow defaults in a config file for options
|
20
|
+
4. allow the options hash to be changed and passed between tasks
|
21
|
+
|
22
|
+
By adding these to thor, Pawnee::Base provides a great starting point for creating recipes. It also tracks any class that inherits from it and adds it to a list of recipes so you can run:
|
23
|
+
|
24
|
+
bundle exec pawnee setup
|
25
|
+
|
26
|
+
Running the above will automatically call setup on all pawnee recipes in the current Gemfile
|
27
|
+
|
28
|
+
## Tasks
|
29
|
+
|
30
|
+
Recipes have two default tasks, #setup and #teardown. For most situations, think of #setup as "install" and #teardown as "uninstall". To run setup now, in your gem directory you can do:
|
31
|
+
|
32
|
+
bundle
|
33
|
+
bundle exec pawnee [gemname] setup
|
34
|
+
|
35
|
+
Also, you could place the gem in another projects Gemfile (using the :path option for now) and then run something like:
|
36
|
+
|
37
|
+
bundle exec pawnee setup
|
38
|
+
|
39
|
+
Again, this will invoke the setup task on all pawnee gems in the Gemfile.
|
40
|
+
|
41
|
+
Tasks can call other tasks using the #invoke method (see thor)
|
42
|
+
|
43
|
+
## Servers Option
|
44
|
+
|
45
|
+
We'll come back to options in a minute, but quickly we have to talk about one special option, 'servers'. 'servers' should be setup as a default option on your setup and teardown tasks.
|
46
|
+
|
47
|
+
Servers can be a few things:
|
48
|
+
|
49
|
+
1. an array of domains (with an assumed default user of ubuntu)
|
50
|
+
2. an array of hashs like the following:
|
51
|
+
[{'domain' => 'something.com', 'roles' => ['your gemname']}, ...]
|
52
|
+
3. a mix of either
|
53
|
+
|
54
|
+
These options can either be passed in on the command line like so:
|
55
|
+
|
56
|
+
bundle exec pawnee yourgemname setup --servers something.com
|
57
|
+
|
58
|
+
Or they can be setup in the [config.yml file](https://github.com/ryanstout/pawnee#config-file).
|
59
|
+
|
60
|
+
## Actions
|
61
|
+
|
62
|
+
### Thor Actions
|
63
|
+
|
64
|
+
Since Pawnee is build on thor, we can use any of the [thor actions](https://github.com/wycats/thor/wiki/Actions) inside our class. Pawnee includes a gem called thor-ssh (which was written for pawnee) that extends thor and allows a .destination_connection to be set on any thor class. .destination_connection is automatically setup for any task if the 'servers' option is set (again, either from the command line or in the config.yml file) When destination_connection is set, any of the actions will use the local machine for the source, and the destination machine for the destination.
|
65
|
+
|
66
|
+
So running something like the following:
|
67
|
+
|
68
|
+
copy_file('templates/test.txt', '/home/ubuntu/test.txt')
|
69
|
+
|
70
|
+
Would copy the file from templates/test.txt (in the gem) to /home/ubuntu/test.txt on the remote server (or servers if multiple servers were passed in)
|
71
|
+
|
72
|
+
**note**: in the current system, if multiple servers are passed in, the task is simply run once for each server.
|
73
|
+
|
74
|
+
thor-ssh also provides two methods for running any command:
|
75
|
+
|
76
|
+
#run and #exec
|
77
|
+
|
78
|
+
#run will log the command being run, #exec will not
|
79
|
+
|
80
|
+
### Pawnee Actions
|
81
|
+
|
82
|
+
Pawnee also provides its own set of actions for common server tasks.
|
83
|
+
|
84
|
+
install_package, remove_package, compile, create_user, delete_user
|
85
|
+
|
86
|
+
## Options
|
87
|
+
|
88
|
+
Pawnee also uses thor's option system. This means your tasks should use thor's #desc and #method_option. ([Read more here](https://github.com/wycats/thor/wiki/Getting-Started) and [here](https://github.com/wycats/thor/wiki/Method-Options)) This allows for an easily defined set of options for any task. This also allows anyone to see what options the tasks take:
|
89
|
+
|
90
|
+
bundle exec pawnee [yourgemname] help setup
|
91
|
+
|
92
|
+
^ would return a list of the options along with descriptions
|
93
|
+
|
94
|
+
By default pawnee makes it so any method options are scoped to the current gem. However pawnee makes it easy for gems to share options. Any gem can change the self.options hash and it will be passed (in its changed form) to the next task. This means you could easily have one gem provide things like path to another gem. (Just make sure the gem providing the paths is before the 2nd gem in the Gemfile). When sharing options, some options make since to not be scoped to the gem. In this case, you can run the #method_options inside of a global_options do block.
|
95
|
+
|
96
|
+
global_options do
|
97
|
+
method_option :deploy_path, :required => true
|
98
|
+
end
|
99
|
+
|
100
|
+
This makes it easy to have the same option be used between gems.
|
101
|
+
|
102
|
+
|
data/docs/TODO.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
TODO: Provide testing stubs for gems
|
2
|
+
TODO: Add a as_user('user') do .. end option
|
3
|
+
- needs to look to options for how to get to root
|
4
|
+
- have it run all commands as that user (for sftp actions we'll set the own after)
|
5
|
+
- we'll need to change exec and run to work from within a shell session
|
6
|
+
- maybe have an option to run from within a shell, or we could get them into the right place every time
|
7
|
+
- maybe we should add a system to "get you to root, then get you to another user"
|
8
|
+
TODO: Need to make a clear way for pawnee gems (and recipes) to provide actions (example, git gem provides git actions)
|
9
|
+
TODO: Run actions in threads (across multiple servers)
|
10
|
+
TODO: Test to make sure arguments work directly as well (they probably don't right now)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module Pawnee
|
4
|
+
module Actions
|
5
|
+
|
6
|
+
class BaseModel
|
7
|
+
include ActiveModel::Dirty
|
8
|
+
attr_accessor :new_record
|
9
|
+
|
10
|
+
def new_record?
|
11
|
+
!!@new_record
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_attributes(attributes)
|
15
|
+
attributes.each_pair do |key,value|
|
16
|
+
self.send(:"#{key}=", value) if self.respond_to?(:"#{key}=")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.change_attr_accessor(method_names)
|
21
|
+
[method_names].flatten.each do |method_name|
|
22
|
+
self.send(:define_method, :"#{method_name}") do
|
23
|
+
return instance_variable_get("@#{method_name}")
|
24
|
+
end
|
25
|
+
|
26
|
+
self.send(:define_method, :"#{method_name}=") do |val|
|
27
|
+
self.send(:"#{method_name}_will_change!") unless val == instance_variable_get("@#{method_name}")
|
28
|
+
instance_variable_set("@#{method_name}", val)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
module Pawnee
|
2
|
+
module Actions
|
3
|
+
|
4
|
+
# Takes a tar.gz or zip file and unzips it, and runs the
|
5
|
+
# standard ./configure, make, sudo make install
|
6
|
+
#
|
7
|
+
# It attempts to raise an exception at any compilation
|
8
|
+
# failure.
|
9
|
+
#
|
10
|
+
# compile 'http://nginx.org/download/nginx-1.2.0.tar.gz'
|
11
|
+
#
|
12
|
+
# === Parameters
|
13
|
+
# url<String>:: The url to download
|
14
|
+
# temp_dir<String>:: Where the compilation should take place
|
15
|
+
# options<Hash>:: Hash of options, see below
|
16
|
+
#
|
17
|
+
# === Options
|
18
|
+
# #compile requires that you either specify a :bin_file option
|
19
|
+
# that the method can check for on the remote system, or that
|
20
|
+
# you pass a block that returns true if the app has already been
|
21
|
+
# installed
|
22
|
+
#
|
23
|
+
# :bin_file - the name of an executable that the method can check for in the path
|
24
|
+
# :configure - a string of options to pass to the ./configure command.
|
25
|
+
#
|
26
|
+
# === Block
|
27
|
+
# You can also pass a block that if it returns true, it will not
|
28
|
+
# recompile. So the general idea is return true if the exe is already
|
29
|
+
# installed.
|
30
|
+
def compile(url, temp_dir, options={})
|
31
|
+
# TODO: Add invoke/revoke support using action(...), maybe
|
32
|
+
# make things run via Thor::Group
|
33
|
+
|
34
|
+
installed = false
|
35
|
+
if options[:bin_file]
|
36
|
+
# Check if the bin file is installed
|
37
|
+
installed = exec(options[:bin_file]).strip != ''
|
38
|
+
else
|
39
|
+
raise "You must pass :bin_file or a block to compile" unless block_given?
|
40
|
+
installed = yield()
|
41
|
+
end
|
42
|
+
|
43
|
+
if installed
|
44
|
+
say_status :already_compiled, url, :blue
|
45
|
+
return true
|
46
|
+
else
|
47
|
+
# Compile and install
|
48
|
+
Compile.new(self, url, temp_dir, options)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Compile
|
53
|
+
attr_accessor :base
|
54
|
+
attr_accessor :options
|
55
|
+
|
56
|
+
# Sets up a compile object
|
57
|
+
#
|
58
|
+
# === Parameters
|
59
|
+
# base<Thor>:: The main class
|
60
|
+
# url<String>:: The url to download from
|
61
|
+
# temp_dir<String>:: A path to a temporary directory where the compilation
|
62
|
+
# can take place
|
63
|
+
def initialize(base, url, temp_dir, options)
|
64
|
+
@base = base
|
65
|
+
@temp_dir = temp_dir
|
66
|
+
self.options = options
|
67
|
+
@file = File.basename(url)
|
68
|
+
@zip_file = @file[/[.]zip$/]
|
69
|
+
@file_path = File.join(temp_dir, @file)
|
70
|
+
|
71
|
+
# TODO: GET THE FIRST DIRECTORY INSTEAD
|
72
|
+
dir_name = @file.gsub(/[.]tar[.]gz$/, '').gsub(/[.]zip$/, '')
|
73
|
+
@extracted_path = File.join(temp_dir, dir_name)
|
74
|
+
|
75
|
+
@base.say_status 'download and compile', url
|
76
|
+
|
77
|
+
download(url)
|
78
|
+
extract
|
79
|
+
configure
|
80
|
+
make
|
81
|
+
make_install
|
82
|
+
end
|
83
|
+
|
84
|
+
# Uses get to download the file and place it in the temp path
|
85
|
+
def download(url)
|
86
|
+
base.get(url, @file_path)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Extract the compressed file
|
90
|
+
def extract
|
91
|
+
base.say_status 'extract', @file
|
92
|
+
if @zip_file
|
93
|
+
base.exec("cd #{@temp_dir} ; unzip #{@file}")
|
94
|
+
else
|
95
|
+
base.exec("cd #{@temp_dir} ; tar xvfpz #{@file}")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Remove the file
|
99
|
+
base.destination_files.rm_rf(@file_path)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Takes a command and an action_name. It says its running the action_name, then
|
103
|
+
# runs the command and if there is non-zero exit status, it prints out an error,
|
104
|
+
# stdout, and stderr, then raises an exception
|
105
|
+
#
|
106
|
+
# === Parameters
|
107
|
+
# command<String>:: The command to be run
|
108
|
+
# action_name<String>:: An action name (used to explain what is happening)
|
109
|
+
def run_with_failure_handler(command, action_name)
|
110
|
+
base.say_status action_name.downcase, ''
|
111
|
+
stdout, stderr, exit_code, exit_status = base.exec(command, true)
|
112
|
+
|
113
|
+
if exit_code != 0
|
114
|
+
base.say_status :error, "Unable to #{action_name}, see output below", :red
|
115
|
+
puts stdout
|
116
|
+
puts stderr
|
117
|
+
|
118
|
+
raise "Unable to configure #{action_name}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Runs ./configure on the files
|
123
|
+
def configure
|
124
|
+
run_with_failure_handler("cd #{@extracted_path} ; ./configure #{options[:configure] || ''}", 'configure')
|
125
|
+
end
|
126
|
+
|
127
|
+
# Runs make
|
128
|
+
def make
|
129
|
+
run_with_failure_handler("cd #{@extracted_path} ; make", 'make')
|
130
|
+
end
|
131
|
+
|
132
|
+
# Runs sudo make install
|
133
|
+
def make_install
|
134
|
+
run_with_failure_handler("cd #{@extracted_path} ; sudo make install", 'make install')
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Pawnee
|
2
|
+
module Actions
|
3
|
+
|
4
|
+
# Returns true if the package is installed. If a version is
|
5
|
+
# listed, it will make sure it matches the version.
|
6
|
+
#
|
7
|
+
# package_installed? 'memcached'
|
8
|
+
# -> true
|
9
|
+
#
|
10
|
+
# package_installed? 'memcached', '1.4.7-0.1ubuntu1'
|
11
|
+
# -> false
|
12
|
+
#
|
13
|
+
# === Parameters
|
14
|
+
# package_name<String>:: The name of package
|
15
|
+
def package_installed?(package_name, version=nil)
|
16
|
+
installed_version = installed_package_version(package_name)
|
17
|
+
if version
|
18
|
+
return (installed_version == version)
|
19
|
+
else
|
20
|
+
return !!installed_version
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the version of a package that is installed
|
25
|
+
#
|
26
|
+
# installed_package_version 'memcached'
|
27
|
+
# -> 1.4.7-0.1ubuntu1
|
28
|
+
#
|
29
|
+
# === Parameters
|
30
|
+
# package_name<String>:: The name of package
|
31
|
+
def installed_package_version(package_name)
|
32
|
+
packages = nil
|
33
|
+
as_root do
|
34
|
+
packages = exec("dpkg -l")
|
35
|
+
end
|
36
|
+
|
37
|
+
packages.split(/\n/).grep(/^ii /).each do |package|
|
38
|
+
_, name, version = package.split(/\s+/)
|
39
|
+
|
40
|
+
if name == package_name
|
41
|
+
return version
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
return nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Installs a package using the operating system's
|
49
|
+
# package management system (currentl apt-get only)
|
50
|
+
#
|
51
|
+
# install_package 'mysql-server5'
|
52
|
+
# install_package 'gcc', '4.5'
|
53
|
+
#
|
54
|
+
# === Parameters
|
55
|
+
# package_name<String>:: The name of package to be installed
|
56
|
+
# version<String>:: The version of the package
|
57
|
+
def install_package(package_name, version=nil)
|
58
|
+
if package_installed?(package_name)
|
59
|
+
say_status "package already installed", package_name
|
60
|
+
else
|
61
|
+
package_name = "#{package_name}=#{version}" if version
|
62
|
+
as_root do
|
63
|
+
exec("apt-get -y install #{package_name}")
|
64
|
+
end
|
65
|
+
say_status "installed package", package_name.gsub('=', ' ')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Installs a set of packages, use install_package if you
|
70
|
+
# wish to specify a version
|
71
|
+
#
|
72
|
+
# install_packages 'build-essential', 'zlib1g-dev'
|
73
|
+
#
|
74
|
+
# === Parameters
|
75
|
+
# packages<Array>:: An array of strings of package names
|
76
|
+
def install_packages(*package_names)
|
77
|
+
package_names.flatten.each do |package_name|
|
78
|
+
install_package(package_name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Removes package_name
|
83
|
+
#
|
84
|
+
# === Parameters
|
85
|
+
# package_name<String>:: The name of package
|
86
|
+
def remove_package(package_name)
|
87
|
+
if package_installed?(package_name)
|
88
|
+
say_status "removed package", package_name
|
89
|
+
as_root do
|
90
|
+
exec("apt-get -y remove #{package_name}")
|
91
|
+
end
|
92
|
+
else
|
93
|
+
say_status "package not removed", package_name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Removes a set of packages, use remove_package if you
|
98
|
+
# wish to specify a version
|
99
|
+
#
|
100
|
+
# remove_packages 'build-essential', 'zlib1g-dev'
|
101
|
+
#
|
102
|
+
# === Parameters
|
103
|
+
# packages<Array>:: An array of strings of package names
|
104
|
+
def remove_packages(*package_names)
|
105
|
+
package_names.flatten.each do |package_name|
|
106
|
+
remove_package(package_name)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|