hibachi 0.0.1.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +18 -0
  7. data/README.md +197 -0
  8. data/Rakefile +10 -0
  9. data/hibachi.gemspec +31 -0
  10. data/lib/generators/hibachi/install_generator.rb +14 -0
  11. data/lib/generators/hibachi/model_generator.rb +41 -0
  12. data/lib/generators/templates/hibachi.rb +11 -0
  13. data/lib/generators/templates/model.rb.erb +4 -0
  14. data/lib/hibachi/chef_runner.rb +54 -0
  15. data/lib/hibachi/configuration.rb +18 -0
  16. data/lib/hibachi/install_active_job_error.rb +13 -0
  17. data/lib/hibachi/job.rb +7 -0
  18. data/lib/hibachi/model.rb +42 -0
  19. data/lib/hibachi/node.rb +112 -0
  20. data/lib/hibachi/persistence.rb +76 -0
  21. data/lib/hibachi/provisioning.rb +28 -0
  22. data/lib/hibachi/querying.rb +48 -0
  23. data/lib/hibachi/railtie.rb +24 -0
  24. data/lib/hibachi/recipe.rb +48 -0
  25. data/lib/hibachi/version.rb +3 -0
  26. data/lib/hibachi.rb +9 -0
  27. data/spec/dummy/README.rdoc +28 -0
  28. data/spec/dummy/Rakefile +6 -0
  29. data/spec/dummy/app/assets/images/.keep +0 -0
  30. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  31. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  32. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  33. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/mailers/.keep +0 -0
  36. data/spec/dummy/app/models/.keep +0 -0
  37. data/spec/dummy/app/models/concerns/.keep +0 -0
  38. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  39. data/spec/dummy/bin/bundle +3 -0
  40. data/spec/dummy/bin/rails +4 -0
  41. data/spec/dummy/bin/rake +4 -0
  42. data/spec/dummy/config/application.rb +24 -0
  43. data/spec/dummy/config/boot.rb +5 -0
  44. data/spec/dummy/config/database.yml +13 -0
  45. data/spec/dummy/config/environment.rb +5 -0
  46. data/spec/dummy/config/environments/development.rb +37 -0
  47. data/spec/dummy/config/environments/production.rb +83 -0
  48. data/spec/dummy/config/environments/test.rb +39 -0
  49. data/spec/dummy/config/hibachi.rb +6 -0
  50. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  51. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  52. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/dummy/config/initializers/inflections.rb +16 -0
  54. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  55. data/spec/dummy/config/initializers/session_store.rb +3 -0
  56. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  57. data/spec/dummy/config/locales/en.yml +23 -0
  58. data/spec/dummy/config/routes.rb +56 -0
  59. data/spec/dummy/config/secrets.yml +22 -0
  60. data/spec/dummy/config.ru +4 -0
  61. data/spec/dummy/lib/assets/.keep +0 -0
  62. data/spec/dummy/log/.keep +0 -0
  63. data/spec/dummy/public/404.html +67 -0
  64. data/spec/dummy/public/422.html +67 -0
  65. data/spec/dummy/public/500.html +66 -0
  66. data/spec/dummy/public/favicon.ico +0 -0
  67. data/spec/fixtures/chef.json +7 -0
  68. data/spec/hibachi/chef_runner_spec.rb +25 -0
  69. data/spec/hibachi/configuration_spec.rb +29 -0
  70. data/spec/hibachi/model_spec.rb +24 -0
  71. data/spec/hibachi/node_spec.rb +52 -0
  72. data/spec/hibachi/recipe_spec.rb +32 -0
  73. data/spec/hibachi/store_spec.rb +37 -0
  74. data/spec/hibachi_spec.rb +13 -0
  75. data/spec/spec_helper.rb +19 -0
  76. data/spec/support/mock_setting.rb +18 -0
  77. data/spec/support/mock_singleton.rb +5 -0
  78. metadata +299 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 93d51833915516bde538031d378a015bcac41b0d
4
+ data.tar.gz: ca5f2c393ab648c57a620a2f74b2f30fbecb64d7
5
+ SHA512:
6
+ metadata.gz: 6cfc2e1dec973836c824094c604d5c338274eb9883f59ef5fb2e2feedc67056b7e743e0a24770f2f11ccd8629d9b7c9ac5fc66966a8d81e677f6d718d8095d1b
7
+ data.tar.gz: 19c4b64c853cbe399769e6a8759e7c8c4aca6263da7d53258918711cee39784e4c7c1d274d33f86c015926ee077795c65ac9cb866138631cab298015f84bdd08
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/dummy/db
19
+ spec/dummy/log/*.log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color --format=documentation
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.2
6
+ cache: bundle
7
+ deploy:
8
+ provider: rubygems
9
+ api_key: $RUBYGEMS_API_KEY
10
+ on:
11
+ tags: true
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hibachi.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2014 Tom Scott
2
+ All rights reserved.
3
+
4
+ Developed by:
5
+
6
+ Tom Scott
7
+ TelVue Corporation
8
+
9
+ http://www.telvue.com/
10
+
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13
+
14
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers.
15
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution.
16
+ Neither the names of Tom Scott, TelVue Corporation, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # Hibachi
2
+
3
+ Hibachi is a bridge between your Rails application and your Chef
4
+ configuration. It provides a framework for building Rails models
5
+ that persist to a central JSON file rather than a database, as well as
6
+ automatically running Chef in an `ActiveJob` in the background.
7
+
8
+ ## Installation
9
+
10
+ Add the gem to your Gemfile:
11
+
12
+ ```ruby
13
+ gem 'hibachi'
14
+ ```
15
+
16
+ Run the following command to install:
17
+
18
+ ```bash
19
+ $ bundle
20
+ ```
21
+
22
+ Now, you can run the generator to set up Hibachi's configuration
23
+ (optional):
24
+
25
+ ```bash
26
+ $ rails generate hibachi:install
27
+ ```
28
+
29
+ This will generate the following Rails initializer:
30
+
31
+ ```ruby
32
+ require 'hibachi'
33
+
34
+ Hibachi.configure do |config|
35
+ config.chef_json_path = "#{Rails.root}/config/chef.json"
36
+ config.chef_dir = "#{Rails.root}/config/chef"
37
+ config.run_in_background = false # NOTE: will be `true` by default when ActiveJob hits 1.0
38
+ end
39
+ ```
40
+
41
+ ## Configuration
42
+
43
+ Configuration is stored in the `Rails.application.config.hibachi`
44
+ object, which is set up by our Railtie and required upon requiring the
45
+ gem. The default settings are specified above, but you can override them
46
+ either in the generated initializer or in the Rails environment config,
47
+ just use the `config.hibachi` namespace.
48
+
49
+ - **chef_json_path** defines an absolute path to the JSON file that
50
+ dictates user-specified configuration.
51
+ - **chef_dir** defines an absolute path to the Chef repo that has been
52
+ installed on this machine.
53
+ - **run_in_background** is a flag that dictates whether to queue Chef
54
+ runs in a background job or run them directly. The default is false,
55
+ but *will* be true whenever ActiveJob is merged in, as this method has
56
+ the best performance.
57
+
58
+ ## Usage
59
+
60
+ When you want to manipulate settings, generate a new Hibachi model:
61
+
62
+ ```bash
63
+ $ rails generate hibachi:model NetworkInterface name dhcp address netmask gateway
64
+ ```
65
+
66
+ You'll get a file that looks like this:
67
+
68
+ ```ruby
69
+ class NetworkInterface < Hibachi::Model
70
+ recipe :network_interfaces
71
+
72
+ attr_accessor :name, :dhcp, :address, :netmask, :gateway
73
+
74
+ end
75
+ ```
76
+
77
+ `Hibachi::Model` is really just an `ActiveModel::Model` with some added
78
+ sugar. So you can use all the validation and callbacks you'd expect from
79
+ an ActiveModel:
80
+
81
+ ```ruby
82
+ class NetworkInterface < Hibachi::Model
83
+ recipe :network_interfaces
84
+
85
+ attr_accessor :name, :dhcp, :address, :netmask, :gateway
86
+
87
+ validates :name, presence: true
88
+ validates :dhcp, presence: true
89
+
90
+ validates :address, presence: true, :if => :dhcp?
91
+ validates :netmask, presence: true, :if => :dhcp?
92
+ validates :gateway, presence: true, :if => :dhcp?
93
+
94
+ def dhcp?
95
+ !!dhcp
96
+ end
97
+ end
98
+ ```
99
+
100
+ When you want to add a new interface, you can do it as if it were a
101
+ regular ActiveRecord model:
102
+
103
+ ```ruby
104
+ NetworkInterface.create name: 'eth2', dhcp: true
105
+ ```
106
+
107
+ Running `create()` will not only persist this new setting to the JSON,
108
+ but will also run Chef on the box for the recipe provided in the class
109
+ definition.
110
+
111
+ Other AR-like methods such as `update()` and `destroy()` are also
112
+ available. They pretty much take the same parameters.
113
+
114
+ ### Singletons
115
+
116
+ Another concept in configuration is the idea of a "singleton", that is,
117
+ a model that exists without the need for enumeration. For example, say
118
+ you have a recipe that configures Nginx like so:
119
+
120
+ ```ruby
121
+ template "install site configuration" do
122
+ source "site.conf.erb"
123
+ action :create
124
+ end
125
+
126
+ nginx_site node[:app_cookbook][:nginx_site][:server_name] do
127
+ action :enable
128
+ end
129
+ ```
130
+
131
+ You can configure that attribute with your front-end app by generating a
132
+ `Hibachi::Model` like this:
133
+
134
+ ```ruby
135
+ class NginxSite < Hibachi::Model
136
+ recipe :nginx_site, :type => :singleton
137
+ attr_accessor :server_name
138
+ end
139
+ ```
140
+
141
+ This affects the way attributes are both looked up and persisted by
142
+ Hibachi. Instead of treating the attribute as if it was an Array of
143
+ Hashes, Hibachi will write to the attribute directly as a Hash. So when
144
+ doing this:
145
+
146
+ ```ruby
147
+ site = NginxSite.fetch
148
+ site.update :server_name => 'www.example.org'
149
+ ```
150
+
151
+ You'll get **chef.json** that looks like this (using the `:app_cookbook`
152
+ namespace from before):
153
+
154
+ ```json
155
+ {
156
+ "app_cookbook": {
157
+ "nginx_site": {
158
+ "server_name": "www.example.org"
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+
165
+ ## Contributing
166
+
167
+ 1. Fork it ( http://github.com/tubbo/hibachi/fork )
168
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
169
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
170
+ 4. Commit your tests (`git commit -am 'Tests for my feature'`)
171
+ 5. Push to the branch (`git push origin my-new-feature`)
172
+ 6. Create new Pull Request
173
+ 7. ![ship it](https://assets-cdn.github.com/images/icons/emoji/shipit.png)
174
+
175
+ ## License
176
+
177
+ This software is licensed under [The University of Illinois/NCSA Open
178
+ Source License](http://opensource.org/licenses/NCSA)...
179
+
180
+ Copyright (c) 2014 Tom Scott
181
+ All rights reserved.
182
+
183
+ Developed by:
184
+
185
+ Tom Scott
186
+ TelVue Corporation
187
+
188
+ http://www.telvue.com/
189
+
190
+
191
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
192
+
193
+ Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers.
194
+ Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimers in the documentation and/or other materials provided with the distribution.
195
+ Neither the names of Tom Scott, TelVue Corporation, nor the names of its contributors may be used to endorse or promote products derived from this Software without specific prior written permission.
196
+
197
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+ require 'yard'
5
+
6
+ RSpec::Core::RakeTask.new :test
7
+
8
+ YARD::Rake::YardocTask.new :doc do |t|
9
+ t.options = %w(--markup=markdown)
10
+ end
data/hibachi.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'hibachi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "hibachi"
8
+ spec.version = Hibachi::VERSION
9
+ spec.authors = ["Tom Scott"]
10
+ spec.email = ["tscott@telvue.com"]
11
+ spec.summary = %q{A Rails model layer for your Chef configuration.}
12
+ spec.description = "#{spec.summary} Control your Chef configs with Rails."
13
+ spec.homepage = "http://github.com/tubbo/hibachi"
14
+ spec.license = "NCSA"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(spec)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'rails'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.5'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec-rails'
26
+ spec.add_development_dependency 'yard'
27
+ spec.add_development_dependency 'redcarpet' # for docs in markdown
28
+ spec.add_development_dependency 'sqlite3' # for the dummy app
29
+ spec.add_development_dependency 'pry'
30
+ spec.add_development_dependency 'pry-byebug'
31
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Hibachi
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ desc "Installs Hibachi to this Rails app"
7
+ source_root File.expand_path("../../templates", __FILE__)
8
+
9
+ def copy_initializer
10
+ template 'hibachi.rb', 'config/initializers/hibachi.rb'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ require 'rails/generators/named_base'
2
+
3
+ module Hibachi
4
+ module Generators
5
+ class ModelGenerator < Rails::Generators::NamedBase
6
+ include Rails::Generators::ResourceHelpers
7
+
8
+ desc "Generates a model for interacting with the Chef config"
9
+ source_root File.expand_path("../../templates", __FILE__)
10
+
11
+ argument :model_attributes, :type => :array
12
+ class_option :recipe, :type => :string, :description => "Specify recipe"
13
+
14
+ def copy_model_definition
15
+ template 'model.rb.erb', "app/models/#{file_path}.rb"
16
+ end
17
+
18
+ protected
19
+ def model_class
20
+ file_path.classify
21
+ end
22
+
23
+ def symbolized_model_attributes
24
+ ARGV[1..-1].reject { |arg|
25
+ arg =~ /\A--/
26
+ }.map { |arg|
27
+ ":#{arg}"
28
+ }.join ', '
29
+ end
30
+
31
+ def recipe_name
32
+ return "'#{derived_recipe_name}'" if derived_recipe_name =~ /\:/
33
+ ":#{derived_recipe_name}" # prefer symbols
34
+ end
35
+
36
+ def derived_recipe_name
37
+ options[:recipe] || file_path.tableize
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ require 'hibachi'
2
+
3
+ # Configure global Hibachi settings here, such as the location of the log file
4
+ # and Chef JSON file. You my also use the Rails config to manipulate
5
+ # these settings, as all configuration data is stored in the
6
+ # `Rails.application.config.hibachi` namespace.
7
+
8
+ Hibachi.configure do |config|
9
+ #config.chef_json_path = "#{Rails.root}/config/chef.json"
10
+ #config.log_path = "#{Rails.root}/log/hibachi.log"
11
+ end
@@ -0,0 +1,4 @@
1
+ class <%= model_class %> < Hibachi::Model
2
+ recipe <%= recipe_name %>
3
+ attr_accessor <%= symbolized_model_attributes %>
4
+ end
@@ -0,0 +1,54 @@
1
+ require 'hibachi/install_active_job_error'
2
+
3
+ module Hibachi
4
+ module ChefRunner
5
+ # Runs the local Chef::Solo client for the given recipe. Loads the
6
+ # configuration specified at `Hibachi.config.chef_json_path` as
7
+ # Chef JSON, running only the given recipe name as specified in the
8
+ # method call. Used by the model backend to run Chef when they are
9
+ # updated, so configuration stays up to date with the JSON
10
+ # configuration.
11
+ def run_chef recipe, options={}
12
+ return true unless config.run_chef
13
+
14
+ if options[:background]
15
+ run_chef_in_bg(recipe) and return true
16
+ else
17
+ run "touch #{config.log_path}" and
18
+ log "Running Chef for '#{recipe}' at '#{Time.now}'..." and
19
+ chef "-r '#{recipe_name(recipe)}' -J #{config.chef_json_path}"
20
+ end
21
+ end
22
+
23
+ private
24
+ def run_chef_in_bg recipe
25
+ raise InstallActiveJobError unless using_active_job?
26
+ require 'hibachi/job' # we must require it here "conditionally"
27
+ Hibachi::Job.enqueue self
28
+ end
29
+
30
+ def using_active_job?
31
+ defined? ActiveJob::Base
32
+ end
33
+
34
+ def chef options
35
+ run %(cd #{config.chef_dir} && chef-solo -l debug #{options})
36
+ end
37
+
38
+ def log message
39
+ run %(echo "#{message}" >> #{config.chef_json_path})
40
+ end
41
+
42
+ def run command
43
+ `#{command}` and $?.success?
44
+ end
45
+
46
+ def recipe_name name
47
+ if name =~ /\:\:/
48
+ "recipe[#{cookbook}::#{name}]"
49
+ else
50
+ "recipe[#{cookbook}::#{name}::default]"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
1
+ require 'hibachi/railtie'
2
+
3
+ module Hibachi
4
+ # Methods for manipulating Hibachi's configuration settings, which are
5
+ # actually stored in the Rails config. This module is meant to be
6
+ # extend'ed onto the Hibachi main module.
7
+ module Configuration
8
+ # Manipulate the Hibachi configuration.
9
+ def configure
10
+ yield config
11
+ end
12
+
13
+ # Shorthand access to the entire Hibachi configuration.
14
+ def config
15
+ Rails.application.config.hibachi
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ module Hibachi
2
+ # Thrown when `Hibachi::Job` is called, but `ActiveJob::Base` was not
3
+ # defined. Prevents a possibly harder-to-diagnose error.
4
+ class InstallActiveJobError < StandardError
5
+ def initialize
6
+ @message = %{
7
+ You must install ActiveJob to run Hibachi in the background..
8
+
9
+ <https://github.com/rails/activejob>
10
+ }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Hibachi
2
+ class Job < ActiveJob::Base
3
+ def perform(model)
4
+ Hibachi.run_chef model.recipe
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ require 'active_model'
2
+
3
+ require 'hibachi/recipe'
4
+ require 'hibachi/persistence'
5
+ require 'hibachi/provisioning'
6
+ require 'hibachi/querying'
7
+
8
+ module Hibachi
9
+ # A Rails model backend for describing machine configuration and
10
+ # exposing such configuration to manipulation by the end user.
11
+ # Hibachi::Model is subclassed like an ActiveRecord::Base, you define
12
+ # attributes on the class that map to attributes in each model's JSON
13
+ # representation.
14
+ class Model
15
+ include ActiveModel::Model
16
+ include Recipe
17
+ include Persistence
18
+ include Provisioning
19
+ extend Querying
20
+
21
+ # Store all attributes in this Hash.
22
+ attr_reader :attributes
23
+
24
+ # Set attributes to the main collector before assigning them to
25
+ # methods.
26
+ def initialize(from_attrs={})
27
+ @attributes = from_attrs
28
+ super
29
+ end
30
+
31
+ # The JSON representation of each Model object is simply its
32
+ # attributes exposed as JSON.
33
+ def to_json(*arguments)
34
+ @json ||= attributes.to_json
35
+ end
36
+
37
+ protected
38
+ def config
39
+ Hibachi.config
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,112 @@
1
+ module Hibachi
2
+ # Interaction object with the Chef JSON. This class is used to write
3
+ # to and read from the configuration on disk, which is also used by
4
+ # Chef to manage machine configuration. It's called "Node" because
5
+ # that's what Chef Server calls each of the servers it's deploying
6
+ # your code to.
7
+ #
8
+ # All operations on this class are "hard", that is, they will actually
9
+ # write data out to the config file.
10
+ class Node
11
+ include ActiveModel::Model
12
+ include Enumerable
13
+
14
+ attr_accessor :attributes, :file_path
15
+
16
+ validates :file_path, presence: true
17
+
18
+ validate :file_exists
19
+ validate :has_cookbook_attributes
20
+
21
+ # Derive config from file at given path.
22
+ def self.find at_path=""
23
+ node = new file_path: at_path
24
+ node.valid?
25
+ node
26
+ end
27
+
28
+ # Test if the specified file we're supposed to manipulate does in
29
+ # fact exist.
30
+ def exists?
31
+ @exists ||= File.exists? file_path
32
+ end
33
+ alias present? exists?
34
+
35
+ # Iterate through all attributes.
36
+ def each
37
+ attributes.each { |attr| yield attr }
38
+ end
39
+
40
+ delegate :empty?, :to => :attributes
41
+ delegate :any?, :to => :attributes
42
+
43
+ # Find the attribute at a given key.
44
+ def [] key
45
+ attributes[key]
46
+ end
47
+
48
+ # Set the attribute at a given key.
49
+ def []= key, value
50
+ merge! key => value
51
+ end
52
+
53
+ # Merge incoming Hash with the Chef JSON.
54
+ def merge! with_new_attributes={}
55
+ attributes.merge! with_new_attributes
56
+ update!
57
+ end
58
+
59
+ # Delete an attribute from the Hash and write JSON.
60
+ def delete id
61
+ attributes.delete id
62
+ update!
63
+ end
64
+
65
+ # Delete all attributes from this recipe.
66
+ def delete!
67
+ @attributes = {}
68
+ update!
69
+ end
70
+
71
+ # Attributes as initially populated by the parsed JSON file. Scoped
72
+ # by the global cookbook.
73
+ def attributes
74
+ @attributes ||= parsed_json_attributes[Hibachi.config.cookbook] || {}
75
+ end
76
+
77
+ delegate :any?, :to => :attributes
78
+ delegate :empty?, :to => :attributes
79
+
80
+ protected
81
+ # All attributes as parsed from the Chef JSON.
82
+ def parsed_json_attributes
83
+ JSON.parse(chef_json).with_indifferent_access
84
+ end
85
+
86
+ private
87
+ def chef_json
88
+ @raw_json ||= File.read file_path
89
+ end
90
+
91
+ def update!
92
+ File.write file_path, pretty_formatted_json
93
+ true
94
+ rescue StandardError => exception
95
+ logger.error exception.message
96
+ exception.backtrace.each { |line| logger.error line }
97
+ false
98
+ end
99
+
100
+ def pretty_formatted_json
101
+ JSON.pretty_generate Hibachi.config.cookbook => attributes
102
+ end
103
+
104
+ def file_exists
105
+ errors.add :file, "'#{file_path}' does not exist" unless exists?
106
+ end
107
+
108
+ def has_cookbook_attributes
109
+ errors.add :cookbook, "could not be parsed from JSON" unless any?
110
+ end
111
+ end
112
+ end