chef-workflow 0.1.1 → 0.2.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/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
|