├── .gitignore ├── .gitmodules ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── Appraisals ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin └── shadow_puppet ├── examples └── foo.rb ├── gemfiles ├── 2.1.gemfile.lock ├── 2.2.gemfile ├── 2.2.gemfile.lock ├── 2.3.11.gemfile.lock ├── 2.3.gemfile ├── 2.3.gemfile.lock ├── 3.0.gemfile ├── 3.0.gemfile.lock ├── 3.1.gemfile ├── 3.1.gemfile.lock ├── 3.2.gemfile └── 3.2.gemfile.lock ├── lib ├── shadow_puppet.rb └── shadow_puppet │ ├── core_ext.rb │ ├── manifest.rb │ ├── test.rb │ └── version.rb ├── rubocop-todo.yml ├── shadow_puppet.gemspec └── spec ├── cli_spec.rb ├── core_ext_spec.rb ├── fixtures ├── cli_spec_manifest.rb └── manifests.rb ├── manifest_spec.rb ├── spec_helper.rb ├── test_spec.rb └── type_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | doc 3 | tmp 4 | Gemfile.lock 5 | .rvmrc 6 | .ruby-version 7 | .ruby-gemset 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "doc"] 2 | path = doc 3 | url = git://github.com/railsmachine/shadow_puppet.git 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require ./spec/spec_helper.rb --backtrace --color --format d 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: rubocop-todo.yml 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | gemfile: 7 | - gemfiles/2.2.gemfile 8 | - gemfiles/2.3.gemfile 9 | - gemfiles/3.0.gemfile 10 | - gemfiles/3.1.gemfile 11 | - gemfiles/3.2.gemfile 12 | bundler_args: --without debug 13 | # quiet notifications for now 14 | notifications: 15 | email: false 16 | irc: 17 | channels: 18 | - "irc.freenode.org#moonshine" 19 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | # 2.1 lacks deep_merge, so no more love 2 | #appraise "2.1" do 3 | # gem "activesupport", "~> 2.1.0" 4 | #end 5 | 6 | appraise "2.2" do 7 | gem "activesupport", "~> 2.2.0" 8 | end 9 | 10 | 11 | appraise "2.3" do 12 | gem "activesupport", "~> 2.3.0" 13 | end 14 | 15 | appraise "3.0" do 16 | gem "activesupport", "~> 3.0.0" 17 | end 18 | 19 | appraise "3.1" do 20 | gem "activesupport", "~> 3.1.0" 21 | end 22 | 23 | appraise "3.2" do 24 | gem "activesupport", "~> 3.2.0" 25 | end 26 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShadowPuppet 2 | 3 | ShadowPuppet is a Ruby DSL for Puppet, extracted out of the work we at Rails 4 | Machine are doing on [Moonshine](http://blog.railsmachine.com/articles/2009/01/16/moonshine-configuration-management-and-deployment/). 5 | 6 | ShadowPuppet provides a DSL for creating collections ("manifests") of Puppet 7 | Resources in Ruby. For documentation on writing these manifests, please see 8 | ShadowPuppet::Manifest. 9 | 10 | A [binary](http://railsmachine.github.com/shadow_puppet/files/bin/shadow_puppet.html) is provided to parse and execute a 11 | ShadowPuppet::Manifest. 12 | 13 | ## Running the Test Suite 14 | 15 | First time: 16 | 17 | $ gem install bundler 18 | $ bundle install 19 | $ bundle exec rake appraisal:install 20 | 21 | Run against all versions of activesupport: 22 | 23 | $ bundle exec rake appraisal spec 24 | 25 | 26 | Run against a specific one, ie 3.2: 27 | 28 | $ bundle exec rake appraisal:3.2 spec 29 | 30 | 31 | *** 32 | 33 | All content copyright © 2014, [Rails Machine, LLC](http://railsmachine.com) 34 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | require "rubocop/rake_task" 4 | 5 | RSpec::Core::RakeTask.new 6 | #Rubocop::RakeTask.new 7 | 8 | task :default => [:spec, :rubocop] 9 | -------------------------------------------------------------------------------- /bin/shadow_puppet: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #== Sample manifest: 3 | # 4 | # $ cat examples/foo.rb 5 | # class Foo < ShadowPuppet::Manifest 6 | # recipe :demo, :text => 'foo' 7 | # recipe :some_gems 8 | # 9 | # def some_gems 10 | # package 'rails', :ensure => :updated, :provider => :gem 11 | # package 'railsmachine', :ensure => '1.0.5', :provider => :gem, :require => package('capistrano') 12 | # package 'capistrano', :ensure => :updated, :provider => :gem 13 | # end 14 | # 15 | # def demo(options = {}) 16 | # exec 'sample', :command => "echo '#{options[:text]}' > /tmp/sample.txt" 17 | # file '/tmp/sample2.txt', :ensure => :present, :content => Facter.to_hash.inspect 18 | # end 19 | # end 20 | # 21 | #== Executing this manifest: 22 | # 23 | # $ shadow_puppet examples/foo.rb 24 | # notice: /shadow_puppet:19861340/Exec[foo]/returns: executed successfully 25 | # $ 26 | # 27 | #The shadow_puppet binary parses the given ruby code, which is 28 | #expected to contain a class named Foo that inherits from 29 | #ShadowPuppet::Manifest. An instance of this class is created, and the 30 | #execute method is called. All output is printed to the console. 31 | require 'bundler' 32 | 33 | def unindent(string) 34 | indentation = string[/\A\s*/] 35 | string.strip.gsub(/^#{indentation}/, "") 36 | end 37 | 38 | begin 39 | require 'optparse' 40 | require 'shadow_puppet/version' 41 | 42 | ShadowPuppetOptions = Struct.new(:graph, :noop, :ignore) 43 | options = ShadowPuppetOptions.new 44 | 45 | opts = OptionParser.new do |opts| 46 | opts.banner = "Usage: shadow_puppet [options] " 47 | 48 | opts.separator "" 49 | opts.separator "Specific options:" 50 | opts.version = ShadowPuppet::VERSION 51 | 52 | opts.on("--tutorial", "A simple tutorial for usage") do 53 | puts unindent(<<-EOF) 54 | NAME 55 | Shadow Puppet 56 | 57 | AUTHOR 58 | Jesse Newland 59 | jesse@railsmachine.com 60 | 61 | DESCRIPTION 62 | A Ruby DSL for puppet 63 | 64 | EXAMPLES 65 | Sample manifest: 66 | 67 | # foo.rb 68 | class Foo < ShadowPuppet::Manifest 69 | recipe :foo 70 | 71 | def foo 72 | exec :foo, :command => 'echo "foo" > /tmp/foo.txt' 73 | file '/tmp/example.txt', :ensure => :present, :content => Facter.to_hash.inspect 74 | end 75 | end 76 | 77 | Executing this manifest: 78 | 79 | $ shadow_puppet foo.rb 80 | notice: /shadow_puppet:19861340/Exec[foo]/returns: executed successfully 81 | $ 82 | 83 | The shadow_puppet binary parses the given ruby code, which is 84 | expected to contain a class named Foo that inherits from 85 | ShadowPuppet::Manifest. An instance of this class is created, and the 86 | execute method is called. All output is printed to the console. 87 | EOF 88 | 89 | exit(1) 90 | end 91 | 92 | opts.on("-g", "--graph=[FILE]", "File to write graph .dot to") do |graph| 93 | options.graph = graph 94 | end 95 | 96 | opts.on("-n", "--noop", "Run in a no-op or dry-run mode") do 97 | options.noop = true 98 | end 99 | 100 | opts.on("-i", "--ignore", "Always exit with 0 exit code even if a Puppet task fails") do 101 | options.ignore = true 102 | end 103 | 104 | opts.on_tail("-h", "--help", "Show this message") do 105 | puts opts 106 | exit 107 | end 108 | end 109 | 110 | opts.parse! 111 | 112 | # Take any variables set on the command line and update ENV 113 | ARGV.delete_if do |arg| 114 | next unless arg =~ /^(\w+)=(.*)$/ 115 | ENV[$1] = $2 116 | end 117 | 118 | unless filename = ARGV[0] 119 | puts "Error: Manifest filename must be provided\n\n" 120 | puts opts 121 | exit(1) 122 | end 123 | 124 | $LOAD_PATH.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')) 125 | require 'shadow_puppet/core_ext' 126 | require 'shadow_puppet' 127 | require 'fileutils' 128 | 129 | if options.noop 130 | Puppet[:noop] = true 131 | Puppet[:show_diff] = true 132 | end 133 | 134 | Puppet::Util::Log.newdestination(:console) 135 | 136 | klass = File.basename(filename, ".rb") 137 | require filename 138 | manifest = klass.camelize.constantize.new 139 | 140 | if options.graph 141 | manifest.graph_to(klass.classify, options.graph) 142 | else 143 | if manifest.execute! or options.ignore 144 | exit(0) 145 | else 146 | exit(1) 147 | end 148 | end 149 | 150 | rescue Errno::EACCES 151 | puts "Please run shadow_puppet as root" 152 | rescue Exception => e 153 | if e.instance_of?(SystemExit) 154 | raise 155 | else 156 | puts "Uncaught exception: #{e.class}: #{e.message}" 157 | puts "\t#{e.backtrace.join("\n\t")}" 158 | exit(1) 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /examples/foo.rb: -------------------------------------------------------------------------------- 1 | class Foo < ShadowPuppet::Manifest 2 | recipe :demo, :text => 'foo' 3 | recipe :some_gems 4 | 5 | def some_gems 6 | package 'rails', :ensure => :updated, :provider => :gem 7 | package 'railsmachine', :ensure => '1.0.5', :provider => :gem, :require => package('capistrano') 8 | package 'capistrano', :ensure => :updated, :provider => :gem 9 | end 10 | 11 | def demo(options = {}) 12 | exec 'sample', :command => "echo '#{options[:text]}' > /tmp/sample.txt" 13 | file '/tmp/sample2.txt', :ensure => :present, :content => Facter.to_hash.inspect 14 | end 15 | end -------------------------------------------------------------------------------- /gemfiles/2.1.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (2.1.2) 13 | archive-tar-minitar (0.5.2) 14 | builder (3.0.0) 15 | columnize (0.3.4) 16 | diff-lcs (1.1.3) 17 | facter (1.6.1) 18 | git (1.2.5) 19 | highline (1.6.2) 20 | i18n (0.6.0) 21 | jeweler (1.6.4) 22 | bundler (~> 1.0) 23 | git (>= 1.2.5) 24 | rake 25 | json (1.6.1) 26 | linecache (0.46) 27 | rbx-require-relative (> 0.0.4) 28 | linecache19 (0.5.12) 29 | ruby_core_source (>= 0.1.4) 30 | puppet (2.7.3) 31 | facter (>= 1.5.1) 32 | rake (0.9.2) 33 | rbx-require-relative (0.0.5) 34 | rdoc (3.10) 35 | json (~> 1.4) 36 | rspec (2.6.0) 37 | rspec-core (~> 2.6.0) 38 | rspec-expectations (~> 2.6.0) 39 | rspec-mocks (~> 2.6.0) 40 | rspec-core (2.6.4) 41 | rspec-expectations (2.6.0) 42 | diff-lcs (~> 1.1.2) 43 | rspec-mocks (2.6.0) 44 | ruby-debug (0.10.4) 45 | columnize (>= 0.1) 46 | ruby-debug-base (~> 0.10.4.0) 47 | ruby-debug-base (0.10.4) 48 | linecache (>= 0.3) 49 | ruby-debug-base19 (0.11.25) 50 | columnize (>= 0.3.1) 51 | linecache19 (>= 0.5.11) 52 | ruby_core_source (>= 0.1.4) 53 | ruby-debug19 (0.11.6) 54 | columnize (>= 0.3.1) 55 | linecache19 (>= 0.5.11) 56 | ruby-debug-base19 (>= 0.11.19) 57 | ruby_core_source (0.1.5) 58 | archive-tar-minitar (>= 0.5.2) 59 | 60 | PLATFORMS 61 | ruby 62 | 63 | DEPENDENCIES 64 | activesupport (~> 2.1.0) 65 | appraisal! 66 | builder (>= 2.1.2) 67 | highline (>= 1.5.0) 68 | i18n (>= 0.5.0) 69 | jeweler (~> 1.6.2) 70 | puppet (= 2.7.3) 71 | rake 72 | rdoc 73 | rspec (~> 2.6.0) 74 | rspec-core (~> 2.6.0) 75 | ruby-debug 76 | ruby-debug19 77 | -------------------------------------------------------------------------------- /gemfiles/2.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source :rubygems 4 | 5 | gem "puppet", "2.7.3" 6 | gem "highline", ">= 1.5.0" 7 | gem "builder", ">= 2.1.2" 8 | gem "activesupport", "~> 2.2.0" 9 | gem "i18n", ">= 0.5.0" 10 | 11 | group :development do 12 | gem "rake" 13 | gem "appraisal", :git=>"git://github.com/technicalpickles/appraisal.git" 14 | gem "rspec", "~> 2.8.0" 15 | gem "rspec-core", "~> 2.8.0" 16 | gem "test-unit" 17 | gem "jeweler", "~> 1.6.2" 18 | gem "rdoc" 19 | end 20 | 21 | group :debug do 22 | gem "ruby-debug", :platforms=>:ruby_18 23 | gem "ruby-debug19", :platforms=>:ruby_19 24 | end 25 | 26 | -------------------------------------------------------------------------------- /gemfiles/2.2.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (2.2.3) 13 | archive-tar-minitar (0.5.2) 14 | builder (3.0.0) 15 | columnize (0.3.4) 16 | diff-lcs (1.1.3) 17 | facter (1.6.1) 18 | git (1.2.5) 19 | highline (1.6.2) 20 | i18n (0.6.0) 21 | jeweler (1.6.4) 22 | bundler (~> 1.0) 23 | git (>= 1.2.5) 24 | rake 25 | json (1.6.1) 26 | linecache (0.46) 27 | rbx-require-relative (> 0.0.4) 28 | linecache19 (0.5.12) 29 | ruby_core_source (>= 0.1.4) 30 | puppet (2.7.3) 31 | facter (>= 1.5.1) 32 | rake (0.9.2) 33 | rbx-require-relative (0.0.5) 34 | rdoc (3.10) 35 | json (~> 1.4) 36 | rspec (2.8.0) 37 | rspec-core (~> 2.8.0) 38 | rspec-expectations (~> 2.8.0) 39 | rspec-mocks (~> 2.8.0) 40 | rspec-core (2.8.0) 41 | rspec-expectations (2.8.0) 42 | diff-lcs (~> 1.1.2) 43 | rspec-mocks (2.8.0) 44 | ruby-debug (0.10.4) 45 | columnize (>= 0.1) 46 | ruby-debug-base (~> 0.10.4.0) 47 | ruby-debug-base (0.10.4) 48 | linecache (>= 0.3) 49 | ruby-debug-base19 (0.11.25) 50 | columnize (>= 0.3.1) 51 | linecache19 (>= 0.5.11) 52 | ruby_core_source (>= 0.1.4) 53 | ruby-debug19 (0.11.6) 54 | columnize (>= 0.3.1) 55 | linecache19 (>= 0.5.11) 56 | ruby-debug-base19 (>= 0.11.19) 57 | ruby_core_source (0.1.5) 58 | archive-tar-minitar (>= 0.5.2) 59 | test-unit (2.4.5) 60 | 61 | PLATFORMS 62 | ruby 63 | 64 | DEPENDENCIES 65 | activesupport (~> 2.2.0) 66 | appraisal! 67 | builder (>= 2.1.2) 68 | highline (>= 1.5.0) 69 | i18n (>= 0.5.0) 70 | jeweler (~> 1.6.2) 71 | puppet (= 2.7.3) 72 | rake 73 | rdoc 74 | rspec (~> 2.8.0) 75 | rspec-core (~> 2.8.0) 76 | ruby-debug 77 | ruby-debug19 78 | test-unit 79 | -------------------------------------------------------------------------------- /gemfiles/2.3.11.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (2.3.11) 13 | archive-tar-minitar (0.5.2) 14 | builder (3.0.0) 15 | columnize (0.3.4) 16 | diff-lcs (1.1.3) 17 | facter (1.6.1) 18 | git (1.2.5) 19 | highline (1.6.2) 20 | i18n (0.6.0) 21 | jeweler (1.6.4) 22 | bundler (~> 1.0) 23 | git (>= 1.2.5) 24 | rake 25 | json (1.6.1) 26 | linecache (0.46) 27 | rbx-require-relative (> 0.0.4) 28 | linecache19 (0.5.12) 29 | ruby_core_source (>= 0.1.4) 30 | puppet (2.7.3) 31 | facter (>= 1.5.1) 32 | rake (0.9.2) 33 | rbx-require-relative (0.0.5) 34 | rdoc (3.10) 35 | json (~> 1.4) 36 | rspec (2.6.0) 37 | rspec-core (~> 2.6.0) 38 | rspec-expectations (~> 2.6.0) 39 | rspec-mocks (~> 2.6.0) 40 | rspec-core (2.6.4) 41 | rspec-expectations (2.6.0) 42 | diff-lcs (~> 1.1.2) 43 | rspec-mocks (2.6.0) 44 | ruby-debug (0.10.4) 45 | columnize (>= 0.1) 46 | ruby-debug-base (~> 0.10.4.0) 47 | ruby-debug-base (0.10.4) 48 | linecache (>= 0.3) 49 | ruby-debug-base19 (0.11.25) 50 | columnize (>= 0.3.1) 51 | linecache19 (>= 0.5.11) 52 | ruby_core_source (>= 0.1.4) 53 | ruby-debug19 (0.11.6) 54 | columnize (>= 0.3.1) 55 | linecache19 (>= 0.5.11) 56 | ruby-debug-base19 (>= 0.11.19) 57 | ruby_core_source (0.1.5) 58 | archive-tar-minitar (>= 0.5.2) 59 | 60 | PLATFORMS 61 | ruby 62 | 63 | DEPENDENCIES 64 | activesupport (= 2.3.11) 65 | appraisal! 66 | builder (>= 2.1.2) 67 | highline (>= 1.5.0) 68 | i18n (>= 0.5.0) 69 | jeweler (~> 1.6.2) 70 | puppet (= 2.7.3) 71 | rake 72 | rdoc 73 | rspec (~> 2.6.0) 74 | rspec-core (~> 2.6.0) 75 | ruby-debug 76 | ruby-debug19 77 | -------------------------------------------------------------------------------- /gemfiles/2.3.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source :rubygems 4 | 5 | gem "puppet", "2.7.3" 6 | gem "highline", ">= 1.5.0" 7 | gem "builder", ">= 2.1.2" 8 | gem "activesupport", "~> 2.3.0" 9 | gem "i18n", ">= 0.5.0" 10 | 11 | group :development do 12 | gem "rake" 13 | gem "appraisal", :git=>"git://github.com/technicalpickles/appraisal.git" 14 | gem "rspec", "~> 2.8.0" 15 | gem "rspec-core", "~> 2.8.0" 16 | gem "test-unit" 17 | gem "jeweler", "~> 1.6.2" 18 | gem "rdoc" 19 | end 20 | 21 | group :debug do 22 | gem "ruby-debug", :platforms=>:ruby_18 23 | gem "ruby-debug19", :platforms=>:ruby_19 24 | end 25 | 26 | -------------------------------------------------------------------------------- /gemfiles/2.3.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (2.3.14) 13 | archive-tar-minitar (0.5.2) 14 | builder (3.0.0) 15 | columnize (0.3.4) 16 | diff-lcs (1.1.3) 17 | facter (1.6.1) 18 | git (1.2.5) 19 | highline (1.6.2) 20 | i18n (0.6.0) 21 | jeweler (1.6.4) 22 | bundler (~> 1.0) 23 | git (>= 1.2.5) 24 | rake 25 | json (1.6.1) 26 | linecache (0.46) 27 | rbx-require-relative (> 0.0.4) 28 | linecache19 (0.5.12) 29 | ruby_core_source (>= 0.1.4) 30 | puppet (2.7.3) 31 | facter (>= 1.5.1) 32 | rake (0.9.2) 33 | rbx-require-relative (0.0.5) 34 | rdoc (3.10) 35 | json (~> 1.4) 36 | rspec (2.8.0) 37 | rspec-core (~> 2.8.0) 38 | rspec-expectations (~> 2.8.0) 39 | rspec-mocks (~> 2.8.0) 40 | rspec-core (2.8.0) 41 | rspec-expectations (2.8.0) 42 | diff-lcs (~> 1.1.2) 43 | rspec-mocks (2.8.0) 44 | ruby-debug (0.10.4) 45 | columnize (>= 0.1) 46 | ruby-debug-base (~> 0.10.4.0) 47 | ruby-debug-base (0.10.4) 48 | linecache (>= 0.3) 49 | ruby-debug-base19 (0.11.25) 50 | columnize (>= 0.3.1) 51 | linecache19 (>= 0.5.11) 52 | ruby_core_source (>= 0.1.4) 53 | ruby-debug19 (0.11.6) 54 | columnize (>= 0.3.1) 55 | linecache19 (>= 0.5.11) 56 | ruby-debug-base19 (>= 0.11.19) 57 | ruby_core_source (0.1.5) 58 | archive-tar-minitar (>= 0.5.2) 59 | test-unit (2.4.5) 60 | 61 | PLATFORMS 62 | ruby 63 | 64 | DEPENDENCIES 65 | activesupport (~> 2.3.0) 66 | appraisal! 67 | builder (>= 2.1.2) 68 | highline (>= 1.5.0) 69 | i18n (>= 0.5.0) 70 | jeweler (~> 1.6.2) 71 | puppet (= 2.7.3) 72 | rake 73 | rdoc 74 | rspec (~> 2.8.0) 75 | rspec-core (~> 2.8.0) 76 | ruby-debug 77 | ruby-debug19 78 | test-unit 79 | -------------------------------------------------------------------------------- /gemfiles/3.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source :rubygems 4 | 5 | gem "puppet", "2.7.3" 6 | gem "highline", ">= 1.5.0" 7 | gem "builder", ">= 2.1.2" 8 | gem "activesupport", "~> 3.0.0" 9 | gem "i18n", ">= 0.5.0" 10 | 11 | group :development do 12 | gem "rake" 13 | gem "appraisal", :git=>"git://github.com/technicalpickles/appraisal.git" 14 | gem "rspec", "~> 2.8.0" 15 | gem "rspec-core", "~> 2.8.0" 16 | gem "test-unit" 17 | gem "jeweler", "~> 1.6.2" 18 | gem "rdoc" 19 | end 20 | 21 | group :debug do 22 | gem "ruby-debug", :platforms=>:ruby_18 23 | gem "ruby-debug19", :platforms=>:ruby_19 24 | end 25 | 26 | -------------------------------------------------------------------------------- /gemfiles/3.0.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (3.0.10) 13 | archive-tar-minitar (0.5.2) 14 | builder (3.0.0) 15 | columnize (0.3.4) 16 | diff-lcs (1.1.3) 17 | facter (1.6.1) 18 | git (1.2.5) 19 | highline (1.6.2) 20 | i18n (0.6.0) 21 | jeweler (1.6.4) 22 | bundler (~> 1.0) 23 | git (>= 1.2.5) 24 | rake 25 | json (1.6.1) 26 | linecache (0.46) 27 | rbx-require-relative (> 0.0.4) 28 | linecache19 (0.5.12) 29 | ruby_core_source (>= 0.1.4) 30 | puppet (2.7.3) 31 | facter (>= 1.5.1) 32 | rake (0.9.2) 33 | rbx-require-relative (0.0.5) 34 | rdoc (3.10) 35 | json (~> 1.4) 36 | rspec (2.8.0) 37 | rspec-core (~> 2.8.0) 38 | rspec-expectations (~> 2.8.0) 39 | rspec-mocks (~> 2.8.0) 40 | rspec-core (2.8.0) 41 | rspec-expectations (2.8.0) 42 | diff-lcs (~> 1.1.2) 43 | rspec-mocks (2.8.0) 44 | ruby-debug (0.10.4) 45 | columnize (>= 0.1) 46 | ruby-debug-base (~> 0.10.4.0) 47 | ruby-debug-base (0.10.4) 48 | linecache (>= 0.3) 49 | ruby-debug-base19 (0.11.25) 50 | columnize (>= 0.3.1) 51 | linecache19 (>= 0.5.11) 52 | ruby_core_source (>= 0.1.4) 53 | ruby-debug19 (0.11.6) 54 | columnize (>= 0.3.1) 55 | linecache19 (>= 0.5.11) 56 | ruby-debug-base19 (>= 0.11.19) 57 | ruby_core_source (0.1.5) 58 | archive-tar-minitar (>= 0.5.2) 59 | test-unit (2.4.5) 60 | 61 | PLATFORMS 62 | ruby 63 | 64 | DEPENDENCIES 65 | activesupport (~> 3.0.0) 66 | appraisal! 67 | builder (>= 2.1.2) 68 | highline (>= 1.5.0) 69 | i18n (>= 0.5.0) 70 | jeweler (~> 1.6.2) 71 | puppet (= 2.7.3) 72 | rake 73 | rdoc 74 | rspec (~> 2.8.0) 75 | rspec-core (~> 2.8.0) 76 | ruby-debug 77 | ruby-debug19 78 | test-unit 79 | -------------------------------------------------------------------------------- /gemfiles/3.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source :rubygems 4 | 5 | gem "puppet", "2.7.3" 6 | gem "highline", ">= 1.5.0" 7 | gem "builder", ">= 2.1.2" 8 | gem "activesupport", "~> 3.1.0" 9 | gem "i18n", ">= 0.5.0" 10 | 11 | group :development do 12 | gem "rake" 13 | gem "appraisal", :git=>"git://github.com/technicalpickles/appraisal.git" 14 | gem "rspec", "~> 2.8.0" 15 | gem "rspec-core", "~> 2.8.0" 16 | gem "test-unit" 17 | gem "jeweler", "~> 1.6.2" 18 | gem "rdoc" 19 | end 20 | 21 | group :debug do 22 | gem "ruby-debug", :platforms=>:ruby_18 23 | gem "ruby-debug19", :platforms=>:ruby_19 24 | end 25 | 26 | -------------------------------------------------------------------------------- /gemfiles/3.1.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (3.1.1) 13 | multi_json (~> 1.0) 14 | archive-tar-minitar (0.5.2) 15 | builder (3.0.0) 16 | columnize (0.3.4) 17 | diff-lcs (1.1.3) 18 | facter (1.6.1) 19 | git (1.2.5) 20 | highline (1.6.2) 21 | i18n (0.6.0) 22 | jeweler (1.6.4) 23 | bundler (~> 1.0) 24 | git (>= 1.2.5) 25 | rake 26 | json (1.6.1) 27 | linecache (0.46) 28 | rbx-require-relative (> 0.0.4) 29 | linecache19 (0.5.12) 30 | ruby_core_source (>= 0.1.4) 31 | multi_json (1.0.3) 32 | puppet (2.7.3) 33 | facter (>= 1.5.1) 34 | rake (0.9.2) 35 | rbx-require-relative (0.0.5) 36 | rdoc (3.10) 37 | json (~> 1.4) 38 | rspec (2.8.0) 39 | rspec-core (~> 2.8.0) 40 | rspec-expectations (~> 2.8.0) 41 | rspec-mocks (~> 2.8.0) 42 | rspec-core (2.8.0) 43 | rspec-expectations (2.8.0) 44 | diff-lcs (~> 1.1.2) 45 | rspec-mocks (2.8.0) 46 | ruby-debug (0.10.4) 47 | columnize (>= 0.1) 48 | ruby-debug-base (~> 0.10.4.0) 49 | ruby-debug-base (0.10.4) 50 | linecache (>= 0.3) 51 | ruby-debug-base19 (0.11.25) 52 | columnize (>= 0.3.1) 53 | linecache19 (>= 0.5.11) 54 | ruby_core_source (>= 0.1.4) 55 | ruby-debug19 (0.11.6) 56 | columnize (>= 0.3.1) 57 | linecache19 (>= 0.5.11) 58 | ruby-debug-base19 (>= 0.11.19) 59 | ruby_core_source (0.1.5) 60 | archive-tar-minitar (>= 0.5.2) 61 | test-unit (2.4.5) 62 | 63 | PLATFORMS 64 | ruby 65 | 66 | DEPENDENCIES 67 | activesupport (~> 3.1.0) 68 | appraisal! 69 | builder (>= 2.1.2) 70 | highline (>= 1.5.0) 71 | i18n (>= 0.5.0) 72 | jeweler (~> 1.6.2) 73 | puppet (= 2.7.3) 74 | rake 75 | rdoc 76 | rspec (~> 2.8.0) 77 | rspec-core (~> 2.8.0) 78 | ruby-debug 79 | ruby-debug19 80 | test-unit 81 | -------------------------------------------------------------------------------- /gemfiles/3.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source :rubygems 4 | 5 | gem "puppet", "2.7.3" 6 | gem "highline", ">= 1.5.0" 7 | gem "builder", ">= 2.1.2" 8 | gem "activesupport", "~> 3.2.0" 9 | gem "i18n", ">= 0.5.0" 10 | 11 | group :development do 12 | gem "rake" 13 | gem "appraisal", :git=>"git://github.com/technicalpickles/appraisal.git" 14 | gem "rspec", "~> 2.8.0" 15 | gem "rspec-core", "~> 2.8.0" 16 | gem "test-unit" 17 | gem "jeweler", "~> 1.6.2" 18 | gem "rdoc" 19 | end 20 | 21 | group :debug do 22 | gem "ruby-debug", :platforms=>:ruby_18 23 | gem "ruby-debug19", :platforms=>:ruby_19 24 | end 25 | 26 | -------------------------------------------------------------------------------- /gemfiles/3.2.gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/technicalpickles/appraisal.git 3 | revision: a755e5a086fa545f13c7465009e3fb8d4b4ff571 4 | specs: 5 | appraisal (0.3.8) 6 | bundler 7 | rake 8 | 9 | GEM 10 | remote: http://rubygems.org/ 11 | specs: 12 | activesupport (3.2.1) 13 | i18n (~> 0.6) 14 | multi_json (~> 1.0) 15 | archive-tar-minitar (0.5.2) 16 | builder (3.0.0) 17 | columnize (0.3.6) 18 | diff-lcs (1.1.3) 19 | facter (1.6.5) 20 | git (1.2.5) 21 | highline (1.6.11) 22 | i18n (0.6.0) 23 | jeweler (1.6.4) 24 | bundler (~> 1.0) 25 | git (>= 1.2.5) 26 | rake 27 | json (1.6.5) 28 | linecache (0.46) 29 | rbx-require-relative (> 0.0.4) 30 | linecache19 (0.5.12) 31 | ruby_core_source (>= 0.1.4) 32 | multi_json (1.0.4) 33 | puppet (2.7.3) 34 | facter (>= 1.5.1) 35 | rake (0.9.2.2) 36 | rbx-require-relative (0.0.5) 37 | rdoc (3.12) 38 | json (~> 1.4) 39 | rspec (2.8.0) 40 | rspec-core (~> 2.8.0) 41 | rspec-expectations (~> 2.8.0) 42 | rspec-mocks (~> 2.8.0) 43 | rspec-core (2.8.0) 44 | rspec-expectations (2.8.0) 45 | diff-lcs (~> 1.1.2) 46 | rspec-mocks (2.8.0) 47 | ruby-debug (0.10.4) 48 | columnize (>= 0.1) 49 | ruby-debug-base (~> 0.10.4.0) 50 | ruby-debug-base (0.10.4) 51 | linecache (>= 0.3) 52 | ruby-debug-base19 (0.11.25) 53 | columnize (>= 0.3.1) 54 | linecache19 (>= 0.5.11) 55 | ruby_core_source (>= 0.1.4) 56 | ruby-debug19 (0.11.6) 57 | columnize (>= 0.3.1) 58 | linecache19 (>= 0.5.11) 59 | ruby-debug-base19 (>= 0.11.19) 60 | ruby_core_source (0.1.5) 61 | archive-tar-minitar (>= 0.5.2) 62 | test-unit (2.4.5) 63 | 64 | PLATFORMS 65 | ruby 66 | 67 | DEPENDENCIES 68 | activesupport (~> 3.2.0) 69 | appraisal! 70 | builder (>= 2.1.2) 71 | highline (>= 1.5.0) 72 | i18n (>= 0.5.0) 73 | jeweler (~> 1.6.2) 74 | puppet (= 2.7.3) 75 | rake 76 | rdoc 77 | rspec (~> 2.8.0) 78 | rspec-core (~> 2.8.0) 79 | ruby-debug 80 | ruby-debug19 81 | test-unit 82 | -------------------------------------------------------------------------------- /lib/shadow_puppet.rb: -------------------------------------------------------------------------------- 1 | require 'shadow_puppet/core_ext' 2 | require 'shadow_puppet/version' 3 | 4 | # Silence puppet's dependencies warnings like: 5 | # racc/parser.rb:27: warning: already initialized constant Racc_Runtime_Version 6 | # racc/parser.rb:28: warning: already initialized constant Racc_Runtime_Revision 7 | # racc/parser.rb:30: warning: already initialized constant Racc_Runtime_Core_Version_R 8 | # racc/parser.rb:31: warning: already initialized constant Racc_Runtime_Core_Revision_R 9 | # racc/parser.rb:35: warning: already initialized constant Racc_Runtime_Core_Revision_C 10 | # racc/parser.rb:39: warning: already initialized constant Racc_Main_Parsing_Routine 11 | # racc/parser.rb:40: warning: already initialized constant Racc_YY_Parse_Method 12 | # racc/parser.rb:41: warning: already initialized constant Racc_Runtime_Core_Version 13 | # racc/parser.rb:42: warning: already initialized constant Racc_Runtime_Core_Revision 14 | # racc/parser.rb:43: warning: already initialized constant Racc_Runtime_Type 15 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:102: warning: class variable access from toplevel 16 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:107: warning: class variable access from toplevel 17 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:107: warning: class variable access from toplevel 18 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:108: warning: class variable access from toplevel 19 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:108: warning: class variable access from toplevel 20 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:109: warning: class variable access from toplevel 21 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:109: warning: class variable access from toplevel 22 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/type/tidy.rb:149: warning: class variable access from toplevel 23 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/provider/service/freebsd.rb:8: warning: class variable access from toplevel 24 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/provider/service/freebsd.rb:9: warning: class variable access from toplevel 25 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/provider/service/freebsd.rb:10: warning: class variable access from toplevel 26 | # /usr/lib/ruby/gems/1.9.1/gems/puppet-2.7.3/lib/puppet/provider/service/bsd.rb:11: warning: class variable access from toplevel 27 | # NOTE: Gem.source_index is deprecated, use Specification. It will be removed on or after 2011-11-01. 28 | # Gem.source_index called from /srv/app/current/vendor/plugins/moonshine/lib/moonshine/manifest/rails/rails.rb:223. 29 | # NOTE: Gem::SourceIndex#search is deprecated with no replacement. It will be removed on or after 2011-11-01. 30 | # Gem::SourceIndex#search called from /srv/app/current/vendor/plugins/moonshine/lib/moonshine/manifest/rails/rails.rb:223 31 | $VERBOSE = nil # comment this out to debug 32 | require 'puppet' 33 | require 'erb' 34 | 35 | require 'shadow_puppet/manifest' 36 | 37 | class ShadowPuppet::Manifest::Setup < ShadowPuppet::Manifest 38 | recipe :setup_directories 39 | 40 | def setup_directories() 41 | if Process.uid == 0 42 | file "/var/shadow_puppet", 43 | :ensure => "directory", 44 | :backup => false 45 | file "/etc/shadow_puppet", 46 | :ensure => "directory", 47 | :backup => false 48 | else 49 | file ENV["HOME"] + "/.shadow_puppet", 50 | :ensure => "directory", 51 | :backup => false 52 | file ENV["HOME"] + "/.shadow_puppet/var", 53 | :ensure => "directory", 54 | :backup => false, 55 | :require => file(ENV["HOME"] + "/.shadow_puppet") 56 | end 57 | end 58 | end 59 | 60 | setup = ShadowPuppet::Manifest::Setup.new 61 | setup.execute 62 | -------------------------------------------------------------------------------- /lib/shadow_puppet/core_ext.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | require 'active_support/version' 3 | 4 | # ActiveSupport 3 doesn't automatically load core_ext anymore 5 | if ActiveSupport::VERSION::MAJOR >= 3 6 | require 'active_support/deprecation' 7 | require 'active_support/core_ext' 8 | end 9 | 10 | ActiveSupport::Deprecation.silenced = true 11 | 12 | class Hash #:nodoc: 13 | def deep_symbolize_keys 14 | self.inject({}) { |result, (key, value)| 15 | value = value.deep_symbolize_keys if value.is_a?(Hash) 16 | result[(key.to_sym rescue key) || key] = value 17 | result 18 | } 19 | end 20 | end 21 | 22 | # backport inheritable accessors deprecated in 3.1 and removed in 3.2 23 | if (ActiveSupport::VERSION::MAJOR == 3 and ActiveSupport::VERSION::MINOR >= 1) or ActiveSupport::VERSION::MAJOR > 3 24 | class Class 25 | def class_inheritable_reader(*syms) 26 | syms.each do |sym| 27 | next if sym.is_a?(Hash) 28 | class_eval <<-EOS 29 | def self.#{sym} # def self.before_add_for_comments 30 | read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:before_add_for_comments) 31 | end # end 32 | # 33 | def #{sym} # def before_add_for_comments 34 | self.class.#{sym} # self.class.before_add_for_comments 35 | end # end 36 | EOS 37 | end 38 | end 39 | 40 | def class_inheritable_writer(*syms) 41 | options = syms.extract_options! 42 | syms.each do |sym| 43 | class_eval <<-EOS 44 | def self.#{sym}=(obj) # def self.color=(obj) 45 | write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj) 46 | end # end 47 | # 48 | #{" # 49 | def #{sym}=(obj) # def color=(obj) 50 | self.class.#{sym} = obj # self.class.color = obj 51 | end # end 52 | " unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false 53 | EOS 54 | end 55 | end 56 | 57 | def class_inheritable_accessor(*syms) 58 | class_inheritable_reader(*syms) 59 | class_inheritable_writer(*syms) 60 | end 61 | 62 | def inheritable_attributes 63 | @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES 64 | end 65 | 66 | def write_inheritable_attribute(key, value) 67 | if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) 68 | @inheritable_attributes = {} 69 | end 70 | inheritable_attributes[key] = value 71 | end 72 | 73 | def read_inheritable_attribute(key) 74 | inheritable_attributes[key] 75 | end 76 | 77 | def reset_inheritable_attributes 78 | @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES 79 | end 80 | 81 | private 82 | # Prevent this constant from being created multiple times 83 | EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES) 84 | 85 | def inherited_with_inheritable_attributes(child) 86 | inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes) 87 | 88 | if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) 89 | new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES 90 | else 91 | new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)| 92 | memo.update(key => value.duplicable? ? value.dup : value) 93 | end 94 | end 95 | 96 | child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes) 97 | end 98 | 99 | alias inherited_without_inheritable_attributes inherited 100 | alias inherited inherited_with_inheritable_attributes 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/shadow_puppet/manifest.rb: -------------------------------------------------------------------------------- 1 | module ShadowPuppet 2 | # A Manifest is an executable collection of Puppet Resources[http://reductivelabs.com/trac/puppet/wiki/TypeReference]. 3 | # 4 | # ===Example 5 | # 6 | # class ManifestExample < ShadowPuppet::Manifest 7 | # recipe :sample 8 | # recipe :lamp, :ruby # queue calls to self.lamp and 9 | # # self.ruby when executing 10 | # 11 | # recipe :mysql, { # queue a call to self.mysql 12 | # :root_password => 'OMGSEKRET' # passing the provided hash 13 | # } # as an option 14 | # 15 | # def sample 16 | # exec :foo, :command => 'echo "foo" > /tmp/foo.txt' 17 | # 18 | # package :foo, :ensure => :installed 19 | # 20 | # file '/tmp/example.txt', 21 | # :ensure => :present, 22 | # :contents => Facter.to_hash_inspect, 23 | # :require => package(:foo) 24 | # end 25 | # 26 | # def lamp 27 | # # install a basic LAMP stack 28 | # end 29 | # 30 | # def ruby 31 | # # install a ruby interpreter and tools 32 | # end 33 | # 34 | # def mysql(options) 35 | # # install a mysql server and set the root password to options[:root_password] 36 | # end 37 | # 38 | # end 39 | # 40 | # To execute the above manifest, instantiate it and call execute on it: 41 | # 42 | # m = ManifestExample.new 43 | # m.execute 44 | # 45 | # As shown in the +sample+ method in ManifestExample above, instance 46 | # methods are created for each Puppet::Type available on your system. These 47 | # methods behave identally to the Puppet Resources methods. See here[http://reductivelabs.com/trac/puppet/wiki/TypeReference] 48 | # for documentation on these methods. 49 | # 50 | # To view a list of all defined methods on your system, run: 51 | # 52 | # ruby -rubygems -e 'require "shadow_puppet";puts ShadowPuppet::Manifest.puppet_type_methods' 53 | # 54 | # The use of methods (+sample+, +lamp+, +ruby+, and +mysql+ above) as a 55 | # container for resources facilitates recipie re-use through the use of Ruby 56 | # Modules. For example: 57 | # 58 | # module ApachePuppet 59 | # # Required options: 60 | # # domain 61 | # # path 62 | # def php_vhost(options) 63 | # #... 64 | # end 65 | # end 66 | # 67 | # class MyWebMainfest < ShadowPuppet::Manifest 68 | # include ApachePuppet 69 | # recipe :php_vhost, { 70 | # :domain => 'foo.com', 71 | # :path => '/var/www/apps/foo' 72 | # } 73 | # end 74 | class Manifest 75 | 76 | class_inheritable_accessor :recipes 77 | write_inheritable_attribute(:recipes, []) 78 | attr_reader :catalog 79 | class_inheritable_accessor :__config__ 80 | write_inheritable_attribute(:__config__, Hash.new) 81 | 82 | # Initialize a new instance of this manifest. This can take a 83 | # config hash, which is immediately passed on to the configure 84 | # method 85 | def initialize(config = {}) 86 | if Process.uid == 0 87 | Puppet[:confdir] = File.expand_path("/etc/shadow_puppet") 88 | Puppet[:vardir] = File.expand_path("/var/shadow_puppet") 89 | Puppet[:codedir] = File.expand_path("/etc/shadow_puppet/code") 90 | Puppet[:logdir] = File.expand_path("/var/log/shadow_puppet") 91 | else 92 | Puppet[:confdir] = File.expand_path("~/.shadow_puppet") 93 | Puppet[:vardir] = File.expand_path("~/.shadow_puppet/var") 94 | Puppet[:codedir] = File.expand_path("~/.shadow_puppet/code") 95 | Puppet[:logdir] = File.expand_path("~/shadow_puppet/log") 96 | end 97 | Puppet[:user] = Process.uid 98 | Puppet[:group] = Process.gid 99 | Puppet::Util::Log.newdestination(:console) 100 | Puppet[:diff_args] = "-u" 101 | Puppet.push_context(Puppet.base_context(Puppet.settings), "Update for application's settings") 102 | 103 | configure(config) 104 | @executed = false 105 | @catalog = Puppet::Resource::Catalog.new 106 | @catalog.host_config = false 107 | @catalog.name = self.name 108 | end 109 | 110 | # Declares that the named method or methods will be called whenever 111 | # execute is called on an instance of this class. If the last argument is 112 | # a Hash, this hash is passed as an argument to all provided methods. 113 | # If no options hash is provided, each method is passed the contents of 114 | # configuration[method]. 115 | # 116 | # Subclasses of the Manifest class properly inherit the parent classes' 117 | # calls to recipe. 118 | def self.recipe(*methods) 119 | return nil if methods.nil? || methods == [] # TODO can probably replace with if methods.blank? 120 | methods.each do |meth| 121 | options = methods.extract_options! 122 | options = configuration[meth.to_sym] if options == {} # TODO can probably be replaced with options.blank? 123 | options ||= {} 124 | recipes << [meth.to_sym, options] 125 | end 126 | end 127 | 128 | # Access to a recipe of the class of this instance. 129 | # 130 | # class SampleManifest < ShadowPuppet::Manifest 131 | # def my_recipe 132 | # recipe :other_recipe 133 | # end 134 | # end 135 | def recipe(*methods) 136 | self.class.recipe *methods 137 | end 138 | 139 | # A HashWithIndifferentAccess describing any configuration that has been 140 | # performed on the class. Modify this hash by calling configure: 141 | # 142 | # class SampleManifest < ShadowPuppet::Manifest 143 | # configure(:name => 'test') 144 | # end 145 | # 146 | # >> SampleManifest.configuration 147 | # => {:name => 'test'} 148 | # # 149 | # Subclasses of the Manifest class properly inherit the parent classes' 150 | # configuration. 151 | def self.configuration 152 | __config__.with_indifferent_access 153 | end 154 | 155 | # Access to the configuration of the class of this instance. 156 | # 157 | # class SampleManifest < ShadowPuppet::Manifest 158 | # configure(:name => 'test') 159 | # end 160 | # 161 | # @manifest = SampleManifest.new 162 | # @manifest.configuration[:name] => "test" 163 | def configuration 164 | self.class.configuration 165 | end 166 | 167 | # Define configuration on this manifest. This is useful for storing things 168 | # such as hostnames, password, or usernames that may change between 169 | # different implementations of a shared manifest. Access this hash by 170 | # calling configuration: 171 | # 172 | # class SampleManifest < ShadowPuppet::Manifest 173 | # configure('name' => 'test') 174 | # end 175 | # 176 | # >> SampleManifest.configuration 177 | # => {:name => 'test'} 178 | # # 179 | # Subsequent calls to configure perform a deep_merge of the provided 180 | # hash into the pre-existing configuration. 181 | def self.configure(hash) 182 | __config__.replace(__config__.deep_symbolize_keys.deep_merge(hash.deep_symbolize_keys)) 183 | end 184 | 185 | # Update the configuration of this manifest instance's class. 186 | # 187 | # class SampleManifest < ShadowPuppet::Manifest 188 | # configure({}) 189 | # end 190 | # 191 | # @manifest = SampleManifest.new 192 | # @manifest.configure(:name => "test") 193 | # @manifest.configuration[:name] => "test" 194 | def configure(hash) 195 | self.class.configure(hash) 196 | end 197 | alias_method :configuration=, :configure 198 | 199 | #An array of all methods defined for creation of Puppet Resources 200 | def self.puppet_type_methods 201 | Puppet::Type.eachtype { |t| t.name }.keys.map { |n| n.to_s }.sort.inspect 202 | end 203 | 204 | def name 205 | @name ||= "#{self.class}##{self.object_id}" 206 | end 207 | 208 | #Create an instance method for every type that either creates or references 209 | #a resource 210 | def self.register_puppet_types 211 | Puppet::Type.loadall 212 | Puppet::Type.eachtype do |type| 213 | # remove the method rdoc placeholders 214 | remove_method(type.name) rescue nil 215 | define_method(type.name) do |*args| 216 | resource_or_reference(type, *args) 217 | end 218 | end 219 | end 220 | register_puppet_types 221 | 222 | # Returns true if this Manifest respond_to? all methods named by 223 | # calls to recipe, and if this Manifest has not been executed before. 224 | def executable? 225 | self.class.recipes.each do |meth,args| 226 | return false unless respond_to?(meth) 227 | end 228 | return false if executed? 229 | true 230 | end 231 | 232 | def missing_recipes 233 | missing = self.class.recipes.each do |meth,args| 234 | !respond_to?(meth) 235 | end 236 | end 237 | 238 | # Execute this manifest, applying all resources defined. Execute returns 239 | # true if successfull, and false if unsucessfull. By default, this 240 | # will only execute a manifest that has not already been executed?. 241 | # The +force+ argument, if true, removes this check. 242 | def execute(force=false) 243 | return false if executed? && !force 244 | evaluate_recipes 245 | transaction = apply 246 | rescue Exception => e 247 | false 248 | else 249 | not transaction.any_failed? 250 | ensure 251 | @executed = true 252 | end 253 | 254 | # Execute this manifest, applying all resources defined. Execute returns 255 | # true if successfull, and raises an exception if not. By default, this 256 | # will only execute a manifest that has not already been executed?. 257 | # The +force+ argument, if true, removes this check. 258 | def execute!(force=false) 259 | return false if executed? && !force 260 | evaluate_recipes 261 | transaction = apply 262 | rescue Exception => e 263 | raise e 264 | else 265 | not transaction.any_failed? 266 | ensure 267 | @executed = true 268 | end 269 | 270 | def graph_to(name, destination) 271 | evaluate_recipes 272 | 273 | relationship_graph = @catalog.relationship_graph 274 | 275 | graph = relationship_graph.to_dot_graph("name" => "#{name} Relationships".gsub(/\W+/, '_')) 276 | graph.options['label'] = "#{name} Relationships" 277 | 278 | # The graph ends up having all of the edges backwards 279 | graph.each_node do |node| 280 | next unless node.is_a?(DOT::DOTEdge) 281 | node.to, node.from = node.from, node.to 282 | end 283 | 284 | File.open(destination, "w") { |f| 285 | f.puts graph.to_s 286 | } 287 | end 288 | 289 | protected 290 | 291 | #Has this manifest instance been executed? 292 | def executed? 293 | @executed 294 | end 295 | 296 | private 297 | 298 | #Evaluate the methods calls queued in self.recipes 299 | def evaluate_recipes 300 | self.class.recipes.each do |meth, args| 301 | case arity = method(meth).arity 302 | when 1, -1 303 | send(meth, args) 304 | else 305 | send(meth) 306 | end 307 | end 308 | end 309 | 310 | # Create a catalog of all contained Puppet Resources and apply that 311 | # catalog to the currently running system 312 | def apply 313 | transaction = catalog.apply 314 | catalog.clear 315 | transaction 316 | end 317 | 318 | def resource_or_reference(type, *args) 319 | if args 320 | if args.flatten.size == 1 321 | reference(type, args.first) 322 | elsif resource = existing_resource(type.name, args.first) 323 | new_resource(type, args.first, args.last).parameters.each do |name, param| 324 | resource[name] = param.value if param.value 325 | end 326 | resource 327 | else 328 | add_resource(type, args.first, args.last) 329 | end 330 | end 331 | end 332 | 333 | def existing_resource(type, title) 334 | catalog.resources.detect { |r| r.type == type && r.title == title } 335 | end 336 | 337 | # Create a reference to another Puppet Resource. 338 | def reference(type, title, params = {}) 339 | Puppet::Resource.new(type.name.to_s.capitalize, title.to_s) 340 | end 341 | 342 | # Creates a new Puppet Resource and adds it to the Catalog. 343 | def add_resource(type, title, params = {}) 344 | catalog.add_resource(new_resource(type, title, params)) 345 | end 346 | 347 | def new_resource(type, title, params = {}) 348 | params.merge!({:title => title.to_s}) 349 | params.merge!({:catalog => catalog}) 350 | params.merge!({:path => ENV["PATH"]}) if type.name == :exec && params[:path].nil? 351 | params.merge!({:cwd => params[:cwd].to_s}) if params[:cwd] 352 | Puppet::Type.type(type.name).new(params) 353 | end 354 | 355 | end 356 | end 357 | -------------------------------------------------------------------------------- /lib/shadow_puppet/test.rb: -------------------------------------------------------------------------------- 1 | module Puppet 2 | class Type 3 | 4 | # clearing out some puppet methods that we probably won't need for testing 5 | # that are also used in the params hash when defining the resource. 6 | undef path 7 | undef require 8 | 9 | # This allows access to resource options as methods on the resource. 10 | def method_missing name, *args 11 | if parameters.keys.include? name.to_sym 12 | parameters[name.to_sym].value 13 | end 14 | end 15 | end 16 | end 17 | 18 | module ShadowPuppet 19 | # To test manifests, access puppet resources using the plural form of the resource name. 20 | # This returns a hash of all resources of that type. 21 | # 22 | # manifest.execs 23 | # manifest.packages 24 | # 25 | # You can access resource options as methods on the resource 26 | # 27 | # manifest.files['/etc/motd'].content 28 | # manifest.execs['service ssh restart'].onlyif 29 | # 30 | # === Example 31 | # 32 | # Given this manifest: 33 | # 34 | # class TestManifest < ShadowPuppet::Manifest 35 | # def myrecipe 36 | # file '/etc/motd', :content => 'Welcome to the machine!', :mode => '644' 37 | # exec 'newaliases', :refreshonly => true 38 | # end 39 | # recipe :myrecipe 40 | # end 41 | # 42 | # A test for the manifest could look like this: 43 | # 44 | # manifest = TestManifest.new 45 | # manifest.myrecipe 46 | # assert_match /Welcome/, manifest.files['/etc/motd'] 47 | # assert manifest.execs['newaliases'].refreshonly 48 | # 49 | class Manifest 50 | # Creates an instance method for every puppet type 51 | # that either creates or references a resource 52 | def self.register_puppet_types_for_testing 53 | Puppet::Type.loadall 54 | Puppet::Type.eachtype do |type| 55 | plural_type = type.name.to_s.downcase.pluralize 56 | #undefine the method rdoc placeholders 57 | undef_method(plural_type) rescue nil 58 | define_method(plural_type) do |*args| 59 | catalog.resources.select { |r| r.type == type.name }.inject({}) do |hash, resource| 60 | hash[resource.title] = resource; hash 61 | end 62 | end 63 | end 64 | end 65 | register_puppet_types_for_testing 66 | 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/shadow_puppet/version.rb: -------------------------------------------------------------------------------- 1 | module ShadowPuppet 2 | VERSION = "0.10.3" 3 | end 4 | -------------------------------------------------------------------------------- /rubocop-todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2014-04-30 16:54:22 -0400 using RuboCop version 0.21.0. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | 8 | # Offense count: 2 9 | # Cop supports --auto-correct. 10 | Alias: 11 | Enabled: false 12 | 13 | # Offense count: 12 14 | # Cop supports --auto-correct. 15 | # Configuration parameters: EnforcedStyle, SupportedStyles. 16 | AlignParameters: 17 | Enabled: false 18 | 19 | # Offense count: 1 20 | AmbiguousOperator: 21 | Enabled: false 22 | 23 | # Offense count: 3 24 | # Cop supports --auto-correct. 25 | AndOr: 26 | Enabled: false 27 | 28 | # Offense count: 2 29 | # Configuration parameters: AllowSafeAssignment. 30 | AssignmentInCondition: 31 | Enabled: false 32 | 33 | # Offense count: 1 34 | BlockAlignment: 35 | Enabled: false 36 | 37 | # Offense count: 3 38 | # Cop supports --auto-correct. 39 | Blocks: 40 | Enabled: false 41 | 42 | # Offense count: 9 43 | # Cop supports --auto-correct. 44 | # Configuration parameters: EnforcedStyle, SupportedStyles. 45 | BracesAroundHashParameters: 46 | Enabled: false 47 | 48 | # Offense count: 1 49 | # Configuration parameters: EnforcedStyle, SupportedStyles. 50 | ClassAndModuleChildren: 51 | Enabled: false 52 | 53 | # Offense count: 1 54 | # Configuration parameters: CountComments. 55 | ClassLength: 56 | Max: 162 57 | 58 | # Offense count: 4 59 | # Cop supports --auto-correct. 60 | # Configuration parameters: PreferredMethods. 61 | CollectionMethods: 62 | Enabled: false 63 | 64 | # Offense count: 2 65 | # Configuration parameters: Keywords. 66 | CommentAnnotation: 67 | Enabled: false 68 | 69 | # Offense count: 1 70 | # Cop supports --auto-correct. 71 | DefWithParentheses: 72 | Enabled: false 73 | 74 | # Offense count: 18 75 | Documentation: 76 | Enabled: false 77 | 78 | # Offense count: 1 79 | # Cop supports --auto-correct. 80 | EmptyLines: 81 | Enabled: false 82 | 83 | # Offense count: 1 84 | EmptyLinesAroundAccessModifier: 85 | Enabled: false 86 | 87 | # Offense count: 7 88 | # Cop supports --auto-correct. 89 | EmptyLinesAroundBody: 90 | Enabled: false 91 | 92 | # Offense count: 1 93 | # Cop supports --auto-correct. 94 | EmptyLiteral: 95 | Enabled: false 96 | 97 | # Offense count: 88 98 | # Cop supports --auto-correct. 99 | # Configuration parameters: SupportedStyles. 100 | HashSyntax: 101 | EnforcedStyle: hash_rockets 102 | 103 | # Offense count: 1 104 | # Configuration parameters: MaxLineLength. 105 | IfUnlessModifier: 106 | Enabled: false 107 | 108 | # Offense count: 1 109 | # Cop supports --auto-correct. 110 | # Configuration parameters: SupportedStyles. 111 | IndentHash: 112 | EnforcedStyle: consistent 113 | 114 | # Offense count: 4 115 | # Cop supports --auto-correct. 116 | IndentationConsistency: 117 | Enabled: false 118 | 119 | # Offense count: 1 120 | # Cop supports --auto-correct. 121 | IndentationWidth: 122 | Enabled: false 123 | 124 | # Offense count: 17 125 | # Cop supports --auto-correct. 126 | LeadingCommentSpace: 127 | Enabled: false 128 | 129 | # Offense count: 73 130 | LineLength: 131 | Max: 134 132 | 133 | # Offense count: 1 134 | # Cop supports --auto-correct. 135 | # Configuration parameters: EnforcedStyle, SupportedStyles. 136 | MethodDefParentheses: 137 | Enabled: false 138 | 139 | # Offense count: 7 140 | # Configuration parameters: CountComments. 141 | MethodLength: 142 | Max: 18 143 | 144 | # Offense count: 2 145 | # Cop supports --auto-correct. 146 | Not: 147 | Enabled: false 148 | 149 | # Offense count: 2 150 | # Cop supports --auto-correct. 151 | # Configuration parameters: PreferredDelimiters. 152 | PercentLiteralDelimiters: 153 | Enabled: false 154 | 155 | # Offense count: 2 156 | # Cop supports --auto-correct. 157 | PerlBackrefs: 158 | Enabled: false 159 | 160 | # Offense count: 3 161 | # Cop supports --auto-correct. 162 | RedundantSelf: 163 | Enabled: false 164 | 165 | # Offense count: 2 166 | # Configuration parameters: MaxSlashes. 167 | RegexpLiteral: 168 | Enabled: false 169 | 170 | # Offense count: 3 171 | # Cop supports --auto-correct. 172 | RescueException: 173 | Enabled: false 174 | 175 | # Offense count: 3 176 | RescueModifier: 177 | Enabled: false 178 | 179 | # Offense count: 1 180 | # Cop supports --auto-correct. 181 | # Configuration parameters: AllowAsExpressionSeparator. 182 | Semicolon: 183 | Enabled: false 184 | 185 | # Offense count: 1 186 | ShadowingOuterLocalVariable: 187 | Enabled: false 188 | 189 | # Offense count: 6 190 | # Cop supports --auto-correct. 191 | SpaceAfterComma: 192 | Enabled: false 193 | 194 | # Offense count: 2 195 | # Cop supports --auto-correct. 196 | # Configuration parameters: EnforcedStyle, SupportedStyles. 197 | SpaceAroundEqualsInParameterDefault: 198 | Enabled: false 199 | 200 | # Offense count: 2 201 | # Cop supports --auto-correct. 202 | SpaceAroundOperators: 203 | Enabled: false 204 | 205 | # Offense count: 4 206 | # Cop supports --auto-correct. 207 | # Configuration parameters: EnforcedStyle, SupportedStyles. 208 | SpaceBeforeBlockBraces: 209 | Enabled: false 210 | 211 | # Offense count: 4 212 | # Cop supports --auto-correct. 213 | # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. 214 | SpaceInsideBlockBraces: 215 | Enabled: true 216 | 217 | # Offense count: 2 218 | # Cop supports --auto-correct. 219 | SpaceInsideBrackets: 220 | Enabled: false 221 | 222 | # Offense count: 18 223 | # Cop supports --auto-correct. 224 | # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. 225 | SpaceInsideHashLiteralBraces: 226 | Enabled: false 227 | 228 | # Offense count: 1 229 | # Cop supports --auto-correct. 230 | SpecialGlobalVars: 231 | Enabled: false 232 | 233 | # Offense count: 138 234 | # Cop supports --auto-correct. 235 | # Configuration parameters: EnforcedStyle, SupportedStyles. 236 | StringLiterals: 237 | Enabled: false 238 | 239 | # Offense count: 3 240 | # Cop supports --auto-correct. 241 | # Configuration parameters: EnforcedStyle, SupportedStyles. 242 | TrailingBlankLines: 243 | Enabled: false 244 | 245 | # Offense count: 10 246 | # Cop supports --auto-correct. 247 | TrailingWhitespace: 248 | Enabled: false 249 | 250 | # Offense count: 1 251 | # Cop supports --auto-correct. 252 | # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, Whitelist. 253 | TrivialAccessors: 254 | Enabled: false 255 | 256 | # Offense count: 4 257 | UnusedBlockArgument: 258 | Enabled: false 259 | 260 | # Offense count: 4 261 | UnusedMethodArgument: 262 | Enabled: false 263 | 264 | # Offense count: 3 265 | UselessAssignment: 266 | Enabled: false 267 | 268 | # Offense count: 13 269 | Void: 270 | Enabled: false 271 | 272 | # Offense count: 1 273 | # Cop supports --auto-correct. 274 | WordArray: 275 | MinSize: 2 276 | -------------------------------------------------------------------------------- /shadow_puppet.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'shadow_puppet/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "shadow_puppet" 8 | spec.version = ShadowPuppet::VERSION 9 | spec.authors = ["Jesse Newland", "Josh Nichols", "Eric Lindvall", 10 | "Lee Jones", "Will Farrington", "dreamcat4", 11 | "Patrick Schless", "Ches Martin", "Rob Lingle", 12 | "Scott Fleckenstein", "Bryan Traywick"] 13 | spec.email = ["bryan@railsmachine.com"] 14 | spec.description = %q{A Ruby Puppet DSL} 15 | spec.summary = %q{A Ruby Puppet DSL} 16 | spec.homepage = "https://github.com/railsmachine/shadow_puppet/" 17 | spec.license = "LGPL" 18 | 19 | spec.files = `git ls-files`.split($/) 20 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 21 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 22 | spec.require_paths = ["lib"] 23 | 24 | spec.add_runtime_dependency "puppet", "~> 4.4.1" 25 | spec.add_runtime_dependency "activesupport", ">= 2.2.0", "< 5.0" 26 | 27 | spec.add_development_dependency "bundler", "~> 1.3" 28 | spec.add_development_dependency "rake" 29 | spec.add_development_dependency "rspec", "~> 2.8.0" 30 | spec.add_development_dependency "rspec-core", "~> 2.8.0" 31 | spec.add_development_dependency "rubocop" 32 | end 33 | -------------------------------------------------------------------------------- /spec/cli_spec.rb: -------------------------------------------------------------------------------- 1 | describe "ShadowPuppet's cli" do 2 | before(:all) do 3 | @root = File.expand_path(File.join(File.dirname(__FILE__), '..')) 4 | @output = `#{@root}/bin/shadow_puppet #{@root}/spec/fixtures/cli_spec_manifest.rb echo=works` 5 | end 6 | 7 | it "accepts env variables on the end of the command line" do 8 | @output.should =~ /Exec\[from_env\]\/returns: works/ 9 | end 10 | 11 | it "outputs the name of the manifest it's executing" do 12 | pending "can't yet determine how to do this" 13 | @output.should =~ /CliSpecManifest/ 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /spec/core_ext_spec.rb: -------------------------------------------------------------------------------- 1 | describe Object do 2 | subject { Object.new } 3 | 4 | it { should respond_to(:present?) } 5 | it { should respond_to(:with_options) } 6 | end 7 | 8 | describe String do 9 | subject { "rake db:migrate spec" } 10 | 11 | it { should respond_to(:present?) } 12 | end 13 | 14 | describe Hash do 15 | subject { {:foo => :bar} } 16 | it { should respond_to(:reverse_merge) } 17 | it { should respond_to(:deep_merge) } 18 | it { should respond_to(:with_indifferent_access) } 19 | end 20 | -------------------------------------------------------------------------------- /spec/fixtures/cli_spec_manifest.rb: -------------------------------------------------------------------------------- 1 | class CliSpecManifest < ShadowPuppet::Manifest 2 | def test_setting_env 3 | exec :from_env, :command => "echo #{ENV['echo']}", :logoutput => true 4 | exec :normal, :command => "echo test", :logoutput => true 5 | end 6 | recipe :test_setting_env 7 | end -------------------------------------------------------------------------------- /spec/fixtures/manifests.rb: -------------------------------------------------------------------------------- 1 | class BlankManifest < ShadowPuppet::Manifest 2 | end 3 | 4 | class SuccessfulManifest < ShadowPuppet::Manifest 5 | recipe :foo 6 | 7 | def foo 8 | exec('foo', :command => 'true') 9 | end 10 | end 11 | 12 | class FailureManifest < SuccessfulManifest 13 | recipe :bar 14 | 15 | def bar 16 | exec('bar', :command => 'false') 17 | end 18 | end 19 | 20 | #this does nothing 21 | class NoOpManifest < ShadowPuppet::Manifest 22 | def foo 23 | exec('foo', :command => 'true') 24 | end 25 | 26 | def bar 27 | exec('bar', :command => 'true') 28 | end 29 | end 30 | 31 | #demonstrate the default method of satifying requirements: instance methods 32 | class RequiresMetViaMethods < ShadowPuppet::Manifest 33 | recipe :foo, :bar 34 | 35 | configure({ :foo => :bar , :nested_hash => { :nested_foo => :bar }, 'string' => 'value' }) 36 | 37 | def foo 38 | exec('foo', :command => 'true') 39 | end 40 | 41 | def bar 42 | exec('bar', :command => 'true') 43 | end 44 | end 45 | 46 | class RequiresMetViaMethodsSubclass < RequiresMetViaMethods 47 | recipe :baz 48 | 49 | configure({ :baz => :bar, :nested_hash => { :nested_baz => :bar } }) 50 | 51 | def baz 52 | exec('baz', :command => 'true') 53 | end 54 | end 55 | 56 | # Requirements can be handled by other recipes in the class 57 | class RequiresMetViaRecipeFromClassOfInstance < ShadowPuppet::Manifest 58 | def bar 59 | # other recipe stuff 60 | end 61 | 62 | def foo 63 | recipe :bar 64 | end 65 | recipe :foo 66 | end 67 | 68 | #requirements can also be handled by functions in external modules 69 | class ProvidedViaModules < ShadowPuppet::Manifest 70 | module FooRecipe 71 | def foo 72 | file('/tmp/moonshine_foo', :ensure => 'present', :content => 'foo') 73 | end 74 | end 75 | 76 | module BarRecipe 77 | def bar 78 | file('/tmp/moonshine_bar', :ensure => 'absent') 79 | end 80 | end 81 | include FooRecipe 82 | include BarRecipe 83 | recipe :foo, :bar 84 | end 85 | 86 | #requirements can also be handled by functions in external modules 87 | class PassingArguments < ShadowPuppet::Manifest 88 | def foo(options = {}) 89 | file(options[:name], :ensure => 'present', :content => 'foo') 90 | end 91 | recipe :foo, :name => '/tmp/moonshine_foo' 92 | end 93 | 94 | # since self.respond_to?(:foo) == false, this raises an error when run 95 | class RequirementsNotMet < ShadowPuppet::Manifest 96 | recipe :foo, :bar 97 | 98 | # def foo 99 | # end 100 | 101 | def bar 102 | #this is okay 103 | end 104 | end 105 | 106 | class ConfigurationWithConvention < ShadowPuppet::Manifest 107 | configure(:foo => :bar) 108 | def foo(string) 109 | file('/tmp/moonshine_foo', :ensure => 'present', :content => string.to_s) 110 | end 111 | recipe :foo 112 | end 113 | 114 | # Test MoonshienSetupManifest 115 | class MoonshineSetupManifest < ShadowPuppet::Manifest 116 | 117 | configure( 118 | :deploy_to => "#{ENV['PWD']}/.shadow_puppet_test", 119 | :user => ENV['USER'], 120 | :group => (`uname -a`.match(/Darwin/) ? 'everyone' : ENV['USER']) 121 | ) 122 | 123 | def directories 124 | deploy_to_array = configuration[:deploy_to].split('/') 125 | deploy_to_array.each_with_index do |dir, index| 126 | next if index == 0 || index >= (deploy_to_array.size-1) 127 | file '/'+deploy_to_array[1..index].join('/'), :ensure => :directory 128 | end 129 | 130 | dirs = [ 131 | "#{configuration[:deploy_to]}", 132 | "#{configuration[:deploy_to]}/shared", 133 | "#{configuration[:deploy_to]}/shared/config", 134 | "#{configuration[:deploy_to]}/releases" 135 | ] 136 | 137 | dirs.each do |dir| 138 | file dir, 139 | :ensure => :directory, 140 | :owner => configuration[:user], 141 | :group => configuration[:group] || configuration[:user], 142 | :mode => '775' 143 | end 144 | end 145 | recipe :directories 146 | end 147 | 148 | # setting up a few different resource types to test the test helpers 149 | class TestHelpers < ShadowPuppet::Manifest 150 | 151 | def foo 152 | exec('foo', :command => 'true',:onlyif => 'test `hostname` == "foo"') 153 | package('bar',:ensure => :installed) 154 | file('/tmp/baz', :content => 'bar',:mode => '644',:owner => 'rails', :before => package('bar')) 155 | end 156 | 157 | end 158 | 159 | class DependencyTestManifest < ShadowPuppet::Manifest 160 | def test 161 | exec('foobar', :command => 'true', :before => exec('barbaz')) 162 | exec('trololol', :command => 'true', :alias => "winning") 163 | exec('barbaz', :command => 'true', :require => [exec('foobar'), exec('winning')]) 164 | 165 | end 166 | recipe :test 167 | end 168 | 169 | class StupidTestManifest < ShadowPuppet::Manifest 170 | def my_recipe 171 | exec 'my_command', 172 | :command => 'true', 173 | :require => [ file('/tmp/foo'), exec('jk') ] 174 | file '/tmp/foo', 175 | :content => 'true' 176 | exec 'jk', 177 | :command => 'true' 178 | end 179 | recipe :my_recipe 180 | end 181 | 182 | class CwdCoercionTest < ShadowPuppet::Manifest 183 | def test 184 | tmp = Pathname.new('/tmp') 185 | exec 'true', 186 | :cwd => tmp 187 | file tmp.join('foo'), 188 | :ensure => :present 189 | end 190 | recipe :test 191 | end 192 | 193 | class DuplicateResourceTest < ShadowPuppet::Manifest 194 | def first_resource 195 | exec 'true', 196 | :cwd => Pathname.new('/tmp') 197 | end 198 | 199 | def second_resource 200 | exec 'true', 201 | :cwd => Pathname.new('/tmp'), 202 | :logoutput => true 203 | end 204 | end 205 | 206 | class MultipleRecipeConfigurationTest < ShadowPuppet::Manifest 207 | configure({ 208 | :foo => {:man => 'chu'}, 209 | :bar => {:food => 'yummy'} 210 | }) 211 | recipe :foo, :bar 212 | 213 | def foo(options = {}) 214 | exec 'foo', :command => 'true' 215 | end 216 | 217 | def bar(options = {}) 218 | exec 'bar', :command => 'true' 219 | end 220 | end 221 | -------------------------------------------------------------------------------- /spec/manifest_spec.rb: -------------------------------------------------------------------------------- 1 | describe "A manifest" do 2 | 3 | describe "when successful" do 4 | 5 | before(:each) do 6 | @manifest = SuccessfulManifest.new 7 | end 8 | 9 | it "returns true when executed" do 10 | @manifest.execute.should be_true 11 | end 12 | 13 | end 14 | 15 | describe "with resouces that fail" do 16 | 17 | before(:each) do 18 | @manifest = FailureManifest.new 19 | end 20 | 21 | it "returns false when executed" do 22 | @manifest.execute.should be_false 23 | end 24 | 25 | end 26 | 27 | describe "when blank" do 28 | 29 | before(:each) do 30 | @manifest = BlankManifest.new 31 | end 32 | 33 | it "does nothing" do 34 | @manifest.class.recipes.should == [] 35 | end 36 | 37 | it "returns true when executed" do 38 | @manifest.execute.should be_true 39 | end 40 | 41 | end 42 | 43 | describe "without specified recipes" do 44 | 45 | before(:each) do 46 | @manifest = NoOpManifest.new 47 | end 48 | 49 | it "is executable by default" do 50 | @manifest.should be_executable 51 | end 52 | 53 | describe "when calling instance methods" do 54 | 55 | before(:each) do 56 | @manifest.foo 57 | end 58 | 59 | it "creates resources" do 60 | @manifest.execs.keys.sort.should == ['foo'] 61 | end 62 | 63 | it "applies our customizations to resources" do 64 | @manifest.execs["foo"].path.should == ENV["PATH"].split(':') 65 | end 66 | 67 | describe "and then executing" do 68 | 69 | before(:each) do 70 | @manifest = @manifest.execute 71 | end 72 | 73 | it "returns true" do 74 | @manifest.should be_true 75 | end 76 | 77 | end 78 | 79 | end 80 | 81 | end 82 | 83 | describe "when recipes aren't fullfilled" do 84 | 85 | before(:each) do 86 | @manifest = RequirementsNotMet.new 87 | end 88 | 89 | it "returns false when executed" do 90 | @manifest.execute.should be_false 91 | end 92 | 93 | it "raises an error when executed!" do 94 | lambda { 95 | @manifest.execute! 96 | }.should raise_error(NameError) 97 | end 98 | 99 | end 100 | 101 | describe "in general" do 102 | 103 | before(:each) do 104 | @manifest = RequiresMetViaMethods.new 105 | end 106 | 107 | it "knows what it's supposed to do" do 108 | @manifest.class.recipes.should == [[:foo, {}], [:bar, {}]] 109 | end 110 | 111 | it "loading configuration on the class" do 112 | @manifest.class.configuration[:foo].should == :bar 113 | end 114 | 115 | it "can access the same configuration hash on the instance" do 116 | @manifest.configuration[:foo].should == :bar 117 | end 118 | 119 | it "can access configurations configured using symbols with symbols or strings" do 120 | @manifest.configuration[:foo].should == :bar 121 | @manifest.configuration['foo'].should == :bar 122 | end 123 | 124 | 125 | it "can access configurations configured using strings with symbols or strings" do 126 | @manifest.configuration['string'].should == 'value' 127 | @manifest.configuration[:string].should == 'value' 128 | end 129 | 130 | it "has a name" do 131 | @manifest.name.should == "#{@manifest.class}##{@manifest.object_id}" 132 | end 133 | 134 | describe 'when evaluated' do 135 | 136 | it "calls specified methods" do 137 | @manifest.should_receive(:foo) 138 | @manifest.should_receive(:bar) 139 | @manifest.send(:evaluate_recipes) 140 | end 141 | 142 | it "passes the configuration hash key named by each method if no options given" do 143 | @manifest = ConfigurationWithConvention.new 144 | @manifest.should_receive(:foo).with(:bar).exactly(1).times 145 | @manifest.send(:evaluate_recipes) 146 | end 147 | 148 | it "creates new resources" do 149 | @manifest.should_receive(:add_resource).with(Puppet::Type::Exec, 'foo', :command => 'true').exactly(1).times 150 | @manifest.should_receive(:add_resource).with(Puppet::Type::Exec, 'bar', :command => 'true').exactly(1).times 151 | @manifest.send(:evaluate_recipes) 152 | end 153 | 154 | it "creates new resources" do 155 | @manifest.send(:evaluate_recipes) 156 | @manifest.execs.keys.sort.should == ['bar', 'foo'] 157 | end 158 | 159 | end 160 | 161 | describe "when executed" do 162 | 163 | it "calls evaluate_recipes and apply" do 164 | @manifest.should_receive(:evaluate_recipes, &@manifest.method(:evaluate_recipes)) 165 | @manifest.should_receive(:apply, &@manifest.method(:apply)) 166 | @manifest.execute 167 | end 168 | 169 | it "returns true" do 170 | @manifest.execute.should be_true 171 | end 172 | 173 | it "cannot be executed again" do 174 | @manifest.execute.should be_true 175 | @manifest.execute.should be_false 176 | end 177 | 178 | end 179 | 180 | describe "after execution" do 181 | 182 | before(:each) do 183 | @manifest = ProvidedViaModules.new 184 | @manifest.execute 185 | end 186 | 187 | it "allows creation of other similar resources" do 188 | m = PassingArguments.new 189 | m.execute.should be_true 190 | end 191 | 192 | end 193 | 194 | end 195 | 196 | describe "that subclasses an existing manifest" do 197 | 198 | before(:each) do 199 | @manifest = RequiresMetViaMethodsSubclass.new 200 | end 201 | 202 | it "inherits recipes from the parent class" do 203 | @manifest.class.recipes.map(&:first).should include(:foo, :bar) 204 | @manifest.class.recipes.first.first.should == :foo 205 | end 206 | 207 | it "appends recipes created in the subclass" do 208 | @manifest.class.recipes.map(&:first).should include(:baz) 209 | @manifest.class.recipes.last.first.should == :baz 210 | end 211 | 212 | it "merges it's configuration with that of the parent" do 213 | @manifest.class.configuration[:foo].should == :bar 214 | @manifest.class.configuration[:baz].should == :bar 215 | end 216 | 217 | it "deep_merges it's configuration with that of the parent" do 218 | @manifest.class.configuration[:nested_hash][:nested_baz].should == :bar 219 | @manifest.class.configuration[:nested_hash][:nested_foo].should == :bar 220 | @manifest.class.configuration['nested_hash']['nested_foo'].should == :bar 221 | end 222 | 223 | it "is able to add configuration parameters on the instance" do 224 | @manifest.configure 'boo' => :bar 225 | @manifest.configuration[:boo].should == :bar 226 | @manifest.class.configuration[:boo].should == :bar 227 | end 228 | 229 | end 230 | describe "that has recipes called from other recipes" do 231 | before(:each) do 232 | @manifest = RequiresMetViaRecipeFromClassOfInstance.new 233 | end 234 | 235 | it "is able to call a recipe of the class of this instance" do 236 | @manifest.execute.should be_true 237 | end 238 | end 239 | 240 | describe "when moonshine setup" do 241 | 242 | before(:each) do 243 | @manifest = MoonshineSetupManifest.new 244 | end 245 | 246 | it "include directories recipe" do 247 | @manifest.class.recipes.map(&:first).should include(:directories) 248 | end 249 | 250 | it "calls specified methods" do 251 | @manifest.should_receive(:directories) 252 | @manifest.send(:evaluate_recipes) 253 | end 254 | 255 | it "returns true when executed" do 256 | @manifest.execute.should be_true 257 | end 258 | 259 | end 260 | 261 | describe "when dependency test manifest" do 262 | before(:each) do 263 | @manifest = DependencyTestManifest.new 264 | end 265 | 266 | it "include directories recipe" do 267 | @manifest.class.recipes.map(&:first).should include(:test) 268 | end 269 | 270 | it "calls specified methods" do 271 | @manifest.should_receive(:test) 272 | @manifest.send(:evaluate_recipes) 273 | end 274 | 275 | it "returns true when executed" do 276 | @manifest.execute!.should be_true 277 | end 278 | end 279 | 280 | describe "when referencing files" do 281 | before(:each) do 282 | @manifest = StupidTestManifest.new 283 | end 284 | 285 | it "returns true when executed " do 286 | @manifest.execute!.should be_true 287 | end 288 | end 289 | 290 | describe "when passing a pathname to cwd" do 291 | before(:each) do 292 | @manifest = CwdCoercionTest.new 293 | end 294 | 295 | it "returns true when executed" do 296 | @manifest.execute!.should be_true 297 | end 298 | end 299 | 300 | describe "with multiple calls to the same resource" do 301 | before(:each) do 302 | @manifest = DuplicateResourceTest.new 303 | end 304 | 305 | it "creates the resource the first time" do 306 | @manifest.first_resource 307 | @manifest.execs["true"].cwd.should == '/tmp' 308 | @manifest.execs["true"].logoutput.should eql(:on_failure) 309 | end 310 | 311 | it "updates the resource subsequent times" do 312 | @manifest.first_resource 313 | @manifest.second_resource 314 | @manifest.execs["true"].cwd.should == '/tmp' 315 | @manifest.execs["true"].logoutput.should be_true 316 | end 317 | 318 | it "can execute successfully" do 319 | @manifest.first_resource 320 | @manifest.second_resource 321 | @manifest.execute! 322 | end 323 | end 324 | 325 | describe "when configure is used to configure multiple recipes" do 326 | before(:each) do 327 | @manifest = MultipleRecipeConfigurationTest.new 328 | end 329 | 330 | it "passes the appropriate options to each recipe" do 331 | @manifest.should_receive(:foo).with({'man' => 'chu'}) 332 | @manifest.should_receive(:bar).with({'food' => 'yummy'}) 333 | @manifest.send(:evaluate_recipes) 334 | end 335 | 336 | it "returns true when executed" do 337 | @manifest.execute!.should be_true 338 | end 339 | end 340 | end 341 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler.require(:default, :development) 3 | 4 | $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) 5 | $LOAD_PATH.unshift(File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')) 6 | 7 | require 'shadow_puppet/core_ext' 8 | require 'shadow_puppet' 9 | require 'shadow_puppet/test' 10 | 11 | Dir.glob(File.join(File.expand_path(File.dirname(__FILE__)), 'fixtures', '*.rb')).each do |manifest| 12 | require manifest 13 | end 14 | -------------------------------------------------------------------------------- /spec/test_spec.rb: -------------------------------------------------------------------------------- 1 | describe "ShadowPuppet's test helpers" do 2 | 3 | it "should be created when register_puppet_types_for_testing is called" do 4 | Puppet::Type.newtype(:dummy) {} 5 | Puppet::Type.type(:dummy).provide(:generic) {} 6 | 7 | BlankManifest.new.respond_to?(:dummies).should == false 8 | ShadowPuppet::Manifest.register_puppet_types_for_testing 9 | BlankManifest.new.respond_to?(:dummies).should == true 10 | end 11 | 12 | describe "when used in tests" do 13 | 14 | before do 15 | @manifest = TestHelpers.new 16 | @manifest.foo 17 | end 18 | 19 | it "should allow simple resource lookup" do 20 | @manifest.execs.keys.should == ['foo'] 21 | @manifest.packages.keys.should == ['bar'] 22 | @manifest.files.keys.should == ['/tmp/baz'] 23 | @manifest.crons.keys.should == [] 24 | end 25 | 26 | # making sure that properties such as, e.g the :onlyif condition of Exec[foo] 27 | # can be accessed simply as manifest.execs['foo'].onlyif rather than via the 28 | # param hash 29 | it "should allow referencing params directly" do 30 | @manifest.execs['foo'].command.should == 'true' 31 | end 32 | 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /spec/type_spec.rb: -------------------------------------------------------------------------------- 1 | describe "ShadowPuppet's type loading mechanism" do 2 | it "should create a new type helper methods when register_puppet_types is called" do 3 | Puppet::Type.newtype(:dummy_1) {} 4 | Puppet::Type.type(:dummy_1).provide(:generic) {} 5 | 6 | BlankManifest.new.respond_to?(:dummy_1).should == false 7 | ShadowPuppet::Manifest.register_puppet_types 8 | BlankManifest.new.respond_to?(:dummy_1).should == true 9 | end 10 | end 11 | --------------------------------------------------------------------------------