chef-workflow 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +19 -0
- data/LICENSE.txt +2 -2
- data/README.md +31 -142
- data/bin/chef-workflow-bootstrap +6 -0
- data/chef-workflow.gemspec +4 -1
- data/lib/chef-workflow.rb +41 -39
- data/lib/chef-workflow/support/attr.rb +28 -26
- data/lib/chef-workflow/support/db.rb +26 -0
- data/lib/chef-workflow/support/db/basic.rb +225 -0
- data/lib/chef-workflow/support/db/group.rb +72 -0
- data/lib/chef-workflow/support/debug.rb +47 -45
- data/lib/chef-workflow/support/ec2.rb +136 -134
- data/lib/chef-workflow/support/general.rb +46 -54
- data/lib/chef-workflow/support/generic.rb +27 -23
- data/lib/chef-workflow/support/ip.rb +89 -103
- data/lib/chef-workflow/support/knife-plugin.rb +26 -24
- data/lib/chef-workflow/support/knife.rb +76 -102
- data/lib/chef-workflow/support/scheduler.rb +319 -324
- data/lib/chef-workflow/support/ssh.rb +100 -0
- data/lib/chef-workflow/support/vagrant.rb +34 -30
- data/lib/chef-workflow/support/vm.rb +25 -54
- data/lib/chef-workflow/support/vm/chef_server.rb +28 -19
- data/lib/chef-workflow/support/vm/ec2.rb +135 -106
- data/lib/chef-workflow/support/vm/helpers/knife.rb +26 -0
- data/lib/chef-workflow/support/vm/knife.rb +218 -189
- data/lib/chef-workflow/support/vm/vagrant.rb +90 -74
- data/lib/chef-workflow/version.rb +3 -5
- metadata +57 -4
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
* 0.2.0 (unreleased)
|
2
|
+
* Several missing validation checks and edge races were resolved.
|
3
|
+
* Significant internals refactor -- things that were fast and simple when
|
4
|
+
things were small got less fast and simple later.
|
5
|
+
* Ruby's singleton library used instead of ugly hax
|
6
|
+
* Replace marshal system with a tie-alike database layer built atop sqlite
|
7
|
+
* Everything is namespaced under ChefWorkflow::
|
8
|
+
* Most of the things that break API above were marked deprecated and will print warnings if used.
|
9
|
+
* Provisioners now have a 'report' method which is used in informational
|
10
|
+
tasks to describe the provisioner's unique data.
|
11
|
+
* Fix for knife bootstrap actually DTRT in chef 10.18.x
|
12
|
+
* SSHHelper from testlib was moved here, now that it's used by tasklib as well.
|
13
|
+
* Docs. Lots and Lots and Lots of Docs.
|
14
|
+
|
15
|
+
* 0.1.1 December 21, 2012
|
16
|
+
* Fix gemspec. Here's to touching the stove.
|
17
|
+
|
18
|
+
* 0.1.0 December 21, 2012
|
19
|
+
* Initial public release
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2012 Erik Hollensbe
|
1
|
+
Copyright (c) 2012, 2013 Erik Hollensbe
|
2
2
|
|
3
3
|
MIT License
|
4
4
|
|
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
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.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,145 +1,30 @@
|
|
1
1
|
Chef Workflow Toolkit
|
2
2
|
---------------------
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
and
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Classes
|
30
|
-
-------
|
31
|
-
|
32
|
-
Expect this to be supplemented with linked RDoc that describes the API for each
|
33
|
-
class. For now, though, you'll have to generate the docs yourself or read the
|
34
|
-
source comments.
|
35
|
-
|
36
|
-
At the time of this writing there is not a very consistent namespacing method,
|
37
|
-
expect this to be corrected before the first release.
|
38
|
-
|
39
|
-
Utility libraries:
|
40
|
-
==================
|
41
|
-
|
42
|
-
* `Chef::Workflow::ConfigureHelper` - a mixin which provides easy-to-use methods
|
43
|
-
of driving the various support configuration systems (see below).
|
44
|
-
* `AttrSupport` - a small mixin to supply chef-style instance variable mutators;
|
45
|
-
the kind that are a little more convenient to use with `instance_eval`'d
|
46
|
-
blocks.
|
47
|
-
* `DebugSupport` - mixin which defines a method called `if_debug` which is our
|
48
|
-
gating mechanism for the `CHEF_WORKFLOW_DEBUG` environment variable.
|
49
|
-
* `GenericSupport` - mixin which keeps the configuration interface consistent by
|
50
|
-
providing a `configure` class method that uses `instance_eval` and exposes a
|
51
|
-
pre-configured object under `singleton` which can be manipulated.
|
52
|
-
* `KnifePluginSupport` - mixin which contains a routine called
|
53
|
-
`init_knife_plugin` to simplify configuration of knife plugins, which can
|
54
|
-
then be used as normal objects. Also configures a UI object with `StringIO`
|
55
|
-
so that it can be communicated with optionally.
|
56
|
-
|
57
|
-
Configuration libraries:
|
58
|
-
========================
|
59
|
-
|
60
|
-
These all mixin `GenericSupport` and as a result are expected to be treated as
|
61
|
-
singletons, by accessing their `singleton` class method and configured with the
|
62
|
-
`configure` class method which `instance_eval`'s a block.
|
63
|
-
|
64
|
-
If you are using `chef-workflow-tasklib`, most of the bits here as you have
|
65
|
-
configured them can be described to you via `bundle exec rake chef:show_config`.
|
66
|
-
|
67
|
-
* `GeneralSupport` - "General" configuration attributes that are global to the
|
68
|
-
entire system.
|
69
|
-
* `IPSupport` - Database for associating IP addresses with a server group. See
|
70
|
-
discussion on the scheduler below for more information on server groups. This
|
71
|
-
is generally not configured externally, but by tooling within the system.
|
72
|
-
* `KnifeSupport` - Most configuration regarding chef lives here, and additional
|
73
|
-
network access.
|
74
|
-
* `VagrantSupport` - Specific bits related to using Vagrant, such as the box to
|
75
|
-
be used for provisioning.
|
76
|
-
* `EC2Support` - bits related to driving AWS EC2.
|
77
|
-
|
78
|
-
Scheduler and VM
|
79
|
-
----------------
|
80
|
-
|
81
|
-
The Scheduler and VM system work together to make groups of machines easy to
|
82
|
-
provision.
|
83
|
-
|
84
|
-
The scheduler is responsible for scheduling provisioning of groups of machines
|
85
|
-
which are interdependent with other machines. In other words when machine C
|
86
|
-
depends on B and A to be provisioned, and B and A have no dependencies, and
|
87
|
-
they are all scheduled at the same time, the scheduler will determine that A
|
88
|
-
and B have to be provisioned immediately and as soon as they are provisioned
|
89
|
-
successfully, it will attempt to provision C. Depending on the system
|
90
|
-
controlling the scheduler and its constraints, it can do this in a serial or
|
91
|
-
parallel fashion, the latter of which will attempt to provision as much as
|
92
|
-
possible at the same time, and as things finish will provision things that are
|
93
|
-
satisfied by what finished.
|
94
|
-
|
95
|
-
In other words, provisioning takes a lot of time in a test run, and the
|
96
|
-
scheduler tries very hard to make it take as little time as is reasonably
|
97
|
-
possible given your constraints and the constraints of the system.
|
98
|
-
|
99
|
-
It manages the actual act of provisioning through the VM system, which tracks
|
100
|
-
the state of what's currently provisioned, what has already successfully
|
101
|
-
provisioned (and presumed alive), and what is waiting to be provisoned. The VM
|
102
|
-
class itself is largely responsible for exposing this data to the scheduler,
|
103
|
-
and marshalling its state to disk in the event of a failure so things can be
|
104
|
-
cleaned up or resumed in the case of resources that will always be depended on
|
105
|
-
for a test run.
|
106
|
-
|
107
|
-
In other words, provisioning takes a lot of time in a test run, and the VM
|
108
|
-
system tries very hard to not add more time to this by tracking machines that
|
109
|
-
are already provisioned so they don't have to be re-provisioned, even between
|
110
|
-
runs. It also makes it easy to clean up a bad or stale test run.
|
111
|
-
|
112
|
-
Server Groups
|
113
|
-
=============
|
114
|
-
|
115
|
-
The VM system itself is a mapping of server groups to an array of provisioning
|
116
|
-
"commands", which are implemented as classes with a consistent interface
|
117
|
-
(visitors). A provisioning command may create a [vagrant
|
118
|
-
prison](https://github.com/chef-workflow/vagrant-prison) which contains all the
|
119
|
-
servers for that server group, complete with assigning them a host-only
|
120
|
-
interface and storing that with `IPSupport` so that it can be retrieved by
|
121
|
-
other bits of the test system or task system. Another provisioning command may
|
122
|
-
execute the in-code equivalent of `knife bootstrap` to build out your servers
|
123
|
-
with a role named after the server group. For de-provisioning, the provisioning
|
124
|
-
commands are played in reverse with a `shutdown` call applied to them.
|
125
|
-
|
126
|
-
Scheduler and VM libraries
|
127
|
-
==========================
|
128
|
-
|
129
|
-
* `Scheduler` - this is the meat; if you're driving a new testing system such
|
130
|
-
as rspec, you'll want to get real familiar with the interface presented.
|
131
|
-
* `VM` - marshalling and delegation interface. Most of this is exposed to the
|
132
|
-
scheduler interface.
|
133
|
-
* `VM::VagrantProvisioner` - creates a vagrant prison composed of n servers for
|
134
|
-
a server group with a unique host-only ip for each server.
|
135
|
-
* `VM::EC2Provisioner` - creates a group of servers provisioned on EC2.
|
136
|
-
* `VM::KnifeProvisioner` - the provisioner equivalent of `knife bootstrap`, with
|
137
|
-
additional sanity checks for both converge success and a waiting period for
|
138
|
-
search indexing. On deprovision, deletes the nodes that were created. Will
|
139
|
-
always try to bootstrap a group in parallel.
|
140
|
-
* `VM::ChefServerProvisioner` - similar to `VM::KnifeProvisioner`, it wraps the
|
141
|
-
[knife-server](https://github.com/fnichol/knife-server) toolkit to create a
|
142
|
-
chef server.
|
4
|
+
A system to provide an encompassing toolkit and framework to work with chef
|
5
|
+
servers, their contents, and the networks of machines controlled by them. Your
|
6
|
+
test environment should look a lot like your production environment, and
|
7
|
+
chef-workflow lets you accomplish exactly that.
|
8
|
+
|
9
|
+
It is 3 major parts:
|
10
|
+
|
11
|
+
* This library, which is core functionality for unifying configuration of the
|
12
|
+
system, provisioning machines and maintaining a source of truth that lives
|
13
|
+
outside chef.
|
14
|
+
* [chef-workflow-tasklib](https://github.com/chef-workflow/chef-workflow-tasklib),
|
15
|
+
which is a toolkit that leverages `rake` to provide a common interface for
|
16
|
+
commanding chef-related operations.
|
17
|
+
* [chef-workflow-testlib](https://github.com/chef-workflow/chef-workflow-tasklib),
|
18
|
+
which is a toolkit for real-world integration testing. No mocks, real
|
19
|
+
machines, no bullshit.
|
20
|
+
|
21
|
+
Most of the Meat is on the Wiki
|
22
|
+
-------------------------------
|
23
|
+
|
24
|
+
Our [wiki](https://github.com/chef-workflow/chef-workflow/wiki) contains
|
25
|
+
a fair amount of information, including how to try chef-workflow without
|
26
|
+
actually doing anything more than cloning a repository and running a few
|
27
|
+
commands.
|
143
28
|
|
144
29
|
Contributing
|
145
30
|
------------
|
@@ -156,6 +41,10 @@ without rationale will be rejected immediately.
|
|
156
41
|
Credits
|
157
42
|
-------
|
158
43
|
|
159
|
-
|
160
|
-
|
161
|
-
|
44
|
+
Author: [Erik Hollensbe](https://github.com/erikh)
|
45
|
+
|
46
|
+
These companies have assisted by donating time, financial resources, and
|
47
|
+
employment to those working on chef-workflow. Supporting OSS is really really
|
48
|
+
cool and we should reciprocate.
|
49
|
+
|
50
|
+
* [HotelTonight](http://www.hoteltonight.com)
|
data/bin/chef-workflow-bootstrap
CHANGED
@@ -92,6 +92,12 @@ configure_knife do
|
|
92
92
|
# hitting this limit a lot, increase this value.
|
93
93
|
#
|
94
94
|
# search_index_wait 60
|
95
|
+
|
96
|
+
# this is this list of recipes you want tested with the 'test:recipes' task.
|
97
|
+
# Note that you must have the 'minitest-handler' cookbook available on your
|
98
|
+
# chef server for these to work.
|
99
|
+
#
|
100
|
+
# test_recipes []
|
95
101
|
end
|
96
102
|
|
97
103
|
#
|
data/chef-workflow.gemspec
CHANGED
@@ -5,7 +5,7 @@ require 'chef-workflow/version'
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = "chef-workflow"
|
8
|
-
gem.version =
|
8
|
+
gem.version = ChefWorkflow::VERSION
|
9
9
|
gem.authors = ["Erik Hollensbe"]
|
10
10
|
gem.email = ["erik+github@hollensbe.org"]
|
11
11
|
gem.description = %q{A comprehensive rake-based workflow for chef}
|
@@ -21,6 +21,9 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_dependency 'chef', '~> 10.0'
|
22
22
|
gem.add_dependency 'aws-sdk', '~> 1.7.0'
|
23
23
|
gem.add_dependency 'net-ssh', '~> 2.2.2'
|
24
|
+
gem.add_dependency 'knife-server', '~> 0.3.3'
|
25
|
+
gem.add_dependency 'sqlite3', '~> 1.3.6'
|
26
|
+
gem.add_dependency 'deprecated', '~> 3.0.1'
|
24
27
|
|
25
28
|
gem.add_development_dependency 'rdoc'
|
26
29
|
gem.add_development_dependency 'rake'
|
data/lib/chef-workflow.rb
CHANGED
@@ -7,58 +7,61 @@ require 'chef-workflow/support/ip'
|
|
7
7
|
require 'chef-workflow/support/debug'
|
8
8
|
require 'chef-workflow/support/ec2'
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
if ENV["REFACTOR"]
|
11
|
+
require 'deprecated'
|
12
|
+
Deprecated.set_action(:raise)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ChefWorkflow
|
16
|
+
#
|
17
|
+
# Basic helpers (intended to be mixed in elsewhere) to configure the
|
18
|
+
# various support configuration systems.
|
19
|
+
#
|
20
|
+
module ConfigureHelper
|
21
|
+
#
|
22
|
+
# Configure 'GeneralSupport'
|
23
|
+
#
|
24
|
+
def configure_general(&block)
|
25
|
+
ChefWorkflow::GeneralSupport.configure(&block)
|
26
|
+
end
|
27
|
+
|
12
28
|
#
|
13
|
-
#
|
14
|
-
# various support configuration systems.
|
29
|
+
# Configure 'KnifeSupport'
|
15
30
|
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
def configure_general(&block)
|
21
|
-
GeneralSupport.configure(&block)
|
22
|
-
end
|
31
|
+
def configure_knife(&block)
|
32
|
+
ChefWorkflow::KnifeSupport.configure(&block)
|
33
|
+
end
|
23
34
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
35
|
+
#
|
36
|
+
# Configure 'VagrantSupport'
|
37
|
+
#
|
38
|
+
def configure_vagrant(&block)
|
39
|
+
ChefWorkflow::VagrantSupport.configure(&block)
|
40
|
+
end
|
30
41
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
#
|
39
|
-
# Configure 'IPSupport' - you probably don't need to do this.
|
40
|
-
#
|
41
|
-
def configure_ips(&block)
|
42
|
-
IPSupport.configure(&block)
|
43
|
-
end
|
42
|
+
#
|
43
|
+
# Configure 'IPSupport' - you probably don't need to do this.
|
44
|
+
#
|
45
|
+
def configure_ips(&block)
|
46
|
+
ChefWorkflow::IPSupport.configure(&block)
|
47
|
+
end
|
44
48
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
49
|
+
#
|
50
|
+
# Configure 'EC2Support'
|
51
|
+
#
|
52
|
+
def configure_ec2(&block)
|
53
|
+
ChefWorkflow::EC2Support.configure(&block)
|
51
54
|
end
|
52
55
|
end
|
53
56
|
end
|
54
57
|
|
55
58
|
class << eval("self", TOPLEVEL_BINDING)
|
56
|
-
include
|
59
|
+
include ChefWorkflow::ConfigureHelper
|
57
60
|
end
|
58
61
|
|
59
62
|
if defined? Rake::DSL
|
60
63
|
module Rake::DSL
|
61
|
-
include
|
64
|
+
include ChefWorkflow::ConfigureHelper
|
62
65
|
end
|
63
66
|
end
|
64
67
|
|
@@ -70,4 +73,3 @@ rescue LoadError
|
|
70
73
|
$stderr.puts "There is no chef-workflow-config in your lib directory."
|
71
74
|
$stderr.puts "Please run chef-workflow-bootstrap or add one."
|
72
75
|
end
|
73
|
-
|
@@ -1,31 +1,33 @@
|
|
1
|
-
|
2
|
-
# Mixin to make exposing attr modification via `instance_eval` easier.
|
3
|
-
#
|
4
|
-
module AttrSupport
|
1
|
+
module ChefWorkflow
|
5
2
|
#
|
6
|
-
#
|
7
|
-
# reader that accepts an optional argument. Equivalent to this code for `foo`:
|
3
|
+
# Mixin to make exposing attr modification via `instance_eval` easier.
|
8
4
|
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
5
|
+
module AttrSupport
|
6
|
+
#
|
7
|
+
# Defines an attribute that is both a standard writer, but with an overloaded
|
8
|
+
# reader that accepts an optional argument. Equivalent to this code for `foo`:
|
9
|
+
#
|
10
|
+
# attr_writer :foo
|
11
|
+
#
|
12
|
+
# def foo(*args)
|
13
|
+
# if args.count > 0
|
14
|
+
# @foo = arg
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @foo
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
def fancy_attr(name)
|
21
|
+
class_eval <<-EOF
|
22
|
+
attr_writer :#{name}
|
23
|
+
def #{name}(*args)
|
24
|
+
if args.count > 0
|
25
|
+
@#{name} = args.first
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
@#{name}
|
29
|
+
end
|
30
|
+
EOF
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'delegate'
|
4
|
+
require 'singleton'
|
5
|
+
require 'chef-workflow/support/general'
|
6
|
+
|
7
|
+
module ChefWorkflow
|
8
|
+
class DatabaseSupport < DelegateClass(SQLite3::Database)
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
super(connect)
|
13
|
+
end
|
14
|
+
|
15
|
+
def reconnect
|
16
|
+
close rescue nil
|
17
|
+
__setobj__(connect)
|
18
|
+
end
|
19
|
+
|
20
|
+
def connect
|
21
|
+
vm_file = ChefWorkflow::GeneralSupport.vm_file
|
22
|
+
FileUtils.mkdir_p(File.dirname(vm_file))
|
23
|
+
SQLite3::Database.new(vm_file)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'chef-workflow/support/db'
|
2
|
+
|
3
|
+
module ChefWorkflow
|
4
|
+
class DatabaseSupport
|
5
|
+
class Generic
|
6
|
+
attr_accessor :db
|
7
|
+
|
8
|
+
def initialize(table_name, object_name=nil)
|
9
|
+
raise "a table_name must be provided!" unless table_name
|
10
|
+
|
11
|
+
@table_name = table_name
|
12
|
+
@object_name = object_name
|
13
|
+
post_marshal_init
|
14
|
+
create_table
|
15
|
+
end
|
16
|
+
|
17
|
+
def self._load(value)
|
18
|
+
obj = self.new(*Marshal.load(value))
|
19
|
+
return obj
|
20
|
+
end
|
21
|
+
|
22
|
+
def _dump(level)
|
23
|
+
self.db = nil
|
24
|
+
res = Marshal.dump([@table_name, @object_name])
|
25
|
+
post_marshal_init
|
26
|
+
return res
|
27
|
+
end
|
28
|
+
|
29
|
+
def post_marshal_init
|
30
|
+
@db = ChefWorkflow::DatabaseSupport.instance
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_table
|
34
|
+
raise "Do not use the Generic type directly!"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Object < Generic
|
39
|
+
def [](key)
|
40
|
+
value = @db.execute("select value from #{@table_name} where key=?", [key]).first.first rescue nil
|
41
|
+
return value && Marshal.load(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def []=(key, value)
|
45
|
+
delete(key)
|
46
|
+
@db.execute("insert into #{@table_name} (key, value) values (?, ?)", [key, Marshal.dump(value)])
|
47
|
+
value
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete(key)
|
51
|
+
@db.execute("delete from #{@table_name} where key=?", [key])
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_table
|
55
|
+
@db.execute <<-EOF
|
56
|
+
create table if not exists #{@table_name} (
|
57
|
+
id integer not null primary key autoincrement,
|
58
|
+
key varchar(255) not null,
|
59
|
+
value text not null,
|
60
|
+
UNIQUE(key)
|
61
|
+
)
|
62
|
+
EOF
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Collection < Generic
|
67
|
+
include Enumerable
|
68
|
+
|
69
|
+
def initialize(table_name, object_name)
|
70
|
+
raise "an object_name must be provided!" unless object_name
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class List < Collection
|
76
|
+
def push(val)
|
77
|
+
@db.execute(
|
78
|
+
"insert into #{@table_name} (name, value) values (?, ?)",
|
79
|
+
[@object_name, Marshal.dump(val)]
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
alias << push
|
84
|
+
|
85
|
+
def unshift(val)
|
86
|
+
replace([val] + to_a)
|
87
|
+
end
|
88
|
+
|
89
|
+
def shift
|
90
|
+
to_a.shift
|
91
|
+
end
|
92
|
+
|
93
|
+
def pop
|
94
|
+
to_a.pop
|
95
|
+
end
|
96
|
+
|
97
|
+
def replace(ary)
|
98
|
+
clear
|
99
|
+
|
100
|
+
value_string = ("(?, ?)," * ary.count).chop
|
101
|
+
|
102
|
+
@db.execute(
|
103
|
+
"insert into #{@table_name} (name, value) values #{value_string}",
|
104
|
+
ary.map { |x| [@object_name, Marshal.dump(x)] }.flatten
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
def clear
|
109
|
+
@db.execute("delete from #{@table_name} where name=?", [@object_name])
|
110
|
+
end
|
111
|
+
|
112
|
+
def each
|
113
|
+
to_a.each { |x| yield x }
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_a
|
117
|
+
@db.execute(
|
118
|
+
"select value from #{@table_name} where name=? order by id",
|
119
|
+
[@object_name]
|
120
|
+
).map { |x| Marshal.load(x.first) }
|
121
|
+
end
|
122
|
+
|
123
|
+
def create_table
|
124
|
+
@db.execute <<-EOF
|
125
|
+
create table if not exists #{@table_name} (
|
126
|
+
id integer not null primary key autoincrement,
|
127
|
+
name varchar(255) not null,
|
128
|
+
value text not null
|
129
|
+
)
|
130
|
+
EOF
|
131
|
+
|
132
|
+
@db.execute "create index if not exists #{@table_name}_name_index on #{@table_name} (name)"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class Set < Collection
|
137
|
+
def add(key)
|
138
|
+
@db.execute("insert into #{@table_name} (name, key) values (?, ?)", [@object_name, key])
|
139
|
+
end
|
140
|
+
|
141
|
+
def delete(key)
|
142
|
+
@db.execute("delete from #{@table_name} where name=? and key=?", [@object_name, key])
|
143
|
+
end
|
144
|
+
|
145
|
+
def has_key?(key)
|
146
|
+
@db.execute("select count(*) from #{@table_name} where name=? and key=?", [@object_name, key]).first.first.to_i > 0
|
147
|
+
end
|
148
|
+
|
149
|
+
alias include? has_key?
|
150
|
+
|
151
|
+
def clear
|
152
|
+
@db.execute("delete from #{@table_name} where name=?", [@object_name])
|
153
|
+
end
|
154
|
+
|
155
|
+
def replace(set)
|
156
|
+
clear
|
157
|
+
|
158
|
+
return if set.empty?
|
159
|
+
|
160
|
+
value_string = ("(?, ?)," * set.count).chop
|
161
|
+
|
162
|
+
@db.execute("insert into #{@table_name} (name, key) values #{value_string}", set.map { |x| [@object_name, x] }.flatten)
|
163
|
+
end
|
164
|
+
|
165
|
+
def keys
|
166
|
+
@db.execute("select distinct key from #{@table_name} where name=?", [@object_name]).map(&:first)
|
167
|
+
end
|
168
|
+
|
169
|
+
def each
|
170
|
+
keys.each { |x| yield x }
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_table
|
174
|
+
@db.execute <<-EOF
|
175
|
+
create table if not exists #{@table_name} (
|
176
|
+
id integer not null primary key autoincrement,
|
177
|
+
name varchar(255) not null,
|
178
|
+
key varchar(255) not null,
|
179
|
+
UNIQUE(name, key)
|
180
|
+
)
|
181
|
+
EOF
|
182
|
+
|
183
|
+
@db.execute "create index if not exists #{@table_name}_name_idx on #{@table_name} (name)"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class Map < Set
|
188
|
+
def [](key)
|
189
|
+
value = @db.execute("select value from #{@table_name} where name=? and key=?", [@object_name, key]).first.first rescue nil
|
190
|
+
return value && Marshal.load(value)
|
191
|
+
end
|
192
|
+
|
193
|
+
def []=(key, value)
|
194
|
+
delete(key)
|
195
|
+
@db.execute("insert into #{@table_name} (name, key, value) values (?, ?, ?)", [@object_name, key, Marshal.dump(value)])
|
196
|
+
value
|
197
|
+
end
|
198
|
+
|
199
|
+
def each
|
200
|
+
keys.each do |key|
|
201
|
+
yield key, self[key]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_hash
|
206
|
+
rows = @db.execute("select key, value from #{@table_name} where name=?", [@object_name])
|
207
|
+
Hash[rows.map { |x| [x[0], Marshal.load(x[1])] }]
|
208
|
+
end
|
209
|
+
|
210
|
+
def create_table
|
211
|
+
@db.execute <<-EOF
|
212
|
+
create table if not exists #{@table_name} (
|
213
|
+
id integer not null primary key autoincrement,
|
214
|
+
name varchar(255) not null,
|
215
|
+
key varchar(255) not null,
|
216
|
+
value text not null,
|
217
|
+
UNIQUE(name, key)
|
218
|
+
)
|
219
|
+
EOF
|
220
|
+
|
221
|
+
@db.execute "create index if not exists #{@table_name}_name_idx on #{@table_name} (name)"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|