berkshelf 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.gitignore +20 -0
  2. data/.rbenv-version +1 -0
  3. data/Gemfile +3 -0
  4. data/Guardfile +23 -0
  5. data/LICENSE +22 -0
  6. data/README.rdoc +102 -0
  7. data/Thorfile +47 -0
  8. data/berkshelf.gemspec +39 -0
  9. data/features/config.sample.yml +3 -0
  10. data/features/init_command.feature +40 -0
  11. data/features/install.feature +55 -0
  12. data/features/lockfile.feature +22 -0
  13. data/features/step_definitions/chef_server_steps.rb +13 -0
  14. data/features/step_definitions/cli_steps.rb +56 -0
  15. data/features/step_definitions/filesystem_steps.rb +79 -0
  16. data/features/support/env.rb +55 -0
  17. data/features/update.feature +23 -0
  18. data/features/upload_command.feature +43 -0
  19. data/features/without.feature +25 -0
  20. data/lib/berkshelf.rb +90 -0
  21. data/lib/berkshelf/berksfile.rb +170 -0
  22. data/lib/berkshelf/cached_cookbook.rb +253 -0
  23. data/lib/berkshelf/cookbook_source.rb +139 -0
  24. data/lib/berkshelf/cookbook_source/git_location.rb +54 -0
  25. data/lib/berkshelf/cookbook_source/path_location.rb +27 -0
  26. data/lib/berkshelf/cookbook_source/site_location.rb +206 -0
  27. data/lib/berkshelf/cookbook_store.rb +77 -0
  28. data/lib/berkshelf/core_ext.rb +3 -0
  29. data/lib/berkshelf/core_ext/file.rb +14 -0
  30. data/lib/berkshelf/core_ext/kernel.rb +33 -0
  31. data/lib/berkshelf/core_ext/pathname.rb +24 -0
  32. data/lib/berkshelf/downloader.rb +92 -0
  33. data/lib/berkshelf/dsl.rb +39 -0
  34. data/lib/berkshelf/errors.rb +20 -0
  35. data/lib/berkshelf/generator_files/Berksfile.erb +3 -0
  36. data/lib/berkshelf/generator_files/chefignore +44 -0
  37. data/lib/berkshelf/git.rb +70 -0
  38. data/lib/berkshelf/init_generator.rb +38 -0
  39. data/lib/berkshelf/lockfile.rb +42 -0
  40. data/lib/berkshelf/resolver.rb +176 -0
  41. data/lib/berkshelf/tx_result.rb +12 -0
  42. data/lib/berkshelf/tx_result_set.rb +37 -0
  43. data/lib/berkshelf/uploader.rb +153 -0
  44. data/lib/berkshelf/version.rb +3 -0
  45. data/lib/chef/knife/berks_init.rb +29 -0
  46. data/lib/chef/knife/berks_install.rb +27 -0
  47. data/lib/chef/knife/berks_update.rb +23 -0
  48. data/lib/chef/knife/berks_upload.rb +39 -0
  49. data/spec/fixtures/Berksfile +3 -0
  50. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/README.md +12 -0
  51. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +6 -0
  52. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/recipes/default.rb +8 -0
  53. data/spec/fixtures/cookbooks/example_cookbook/README.md +12 -0
  54. data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +6 -0
  55. data/spec/fixtures/cookbooks/example_cookbook/recipes/default.rb +8 -0
  56. data/spec/fixtures/cookbooks/invalid_ruby_files-1.0.0/recipes/default.rb +1 -0
  57. data/spec/fixtures/cookbooks/invalid_template_files-1.0.0/templates/default/broken.erb +1 -0
  58. data/spec/fixtures/cookbooks/nginx-0.100.5/README.md +77 -0
  59. data/spec/fixtures/cookbooks/nginx-0.100.5/attributes/default.rb +65 -0
  60. data/spec/fixtures/cookbooks/nginx-0.100.5/definitions/nginx_site.rb +35 -0
  61. data/spec/fixtures/cookbooks/nginx-0.100.5/files/default/mime.types +73 -0
  62. data/spec/fixtures/cookbooks/nginx-0.100.5/files/ubuntu/mime.types +73 -0
  63. data/spec/fixtures/cookbooks/nginx-0.100.5/libraries/nginxlib.rb +1 -0
  64. data/spec/fixtures/cookbooks/nginx-0.100.5/metadata.rb +91 -0
  65. data/spec/fixtures/cookbooks/nginx-0.100.5/providers/defprovider.rb +1 -0
  66. data/spec/fixtures/cookbooks/nginx-0.100.5/recipes/default.rb +59 -0
  67. data/spec/fixtures/cookbooks/nginx-0.100.5/resources/defresource.rb +1 -0
  68. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/nginx.pill.erb +15 -0
  69. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/plugins/nginx.rb.erb +66 -0
  70. data/spec/fixtures/lockfile_spec/with_lock/Berksfile +1 -0
  71. data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +5 -0
  72. data/spec/spec_helper.rb +92 -0
  73. data/spec/support/chef_api.rb +27 -0
  74. data/spec/support/matchers/file_system_matchers.rb +115 -0
  75. data/spec/support/matchers/filepath_matchers.rb +19 -0
  76. data/spec/unit/berkshelf/cached_cookbook_spec.rb +420 -0
  77. data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +59 -0
  78. data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +34 -0
  79. data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +166 -0
  80. data/spec/unit/berkshelf/cookbook_source_spec.rb +194 -0
  81. data/spec/unit/berkshelf/cookbook_store_spec.rb +71 -0
  82. data/spec/unit/berkshelf/cookbookfile_spec.rb +160 -0
  83. data/spec/unit/berkshelf/downloader_spec.rb +82 -0
  84. data/spec/unit/berkshelf/dsl_spec.rb +42 -0
  85. data/spec/unit/berkshelf/git_spec.rb +63 -0
  86. data/spec/unit/berkshelf/init_generator_spec.rb +52 -0
  87. data/spec/unit/berkshelf/lockfile_spec.rb +25 -0
  88. data/spec/unit/berkshelf/resolver_spec.rb +126 -0
  89. data/spec/unit/berkshelf/tx_result_set_spec.rb +77 -0
  90. data/spec/unit/berkshelf/tx_result_spec.rb +21 -0
  91. data/spec/unit/berkshelf/uploader_spec.rb +71 -0
  92. data/spec/unit/berkshelf_spec.rb +29 -0
  93. metadata +411 -0
@@ -0,0 +1,37 @@
1
+ module Berkshelf
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class TXResultSet
4
+ attr_reader :results
5
+
6
+ def initialize
7
+ @results = []
8
+ end
9
+
10
+ def add_result(result)
11
+ unless validate_result(result)
12
+ raise ArgumentError, "Invalid Result: results must respond to :failed? and :success?"
13
+ end
14
+
15
+ @results << result
16
+ end
17
+
18
+ def failed
19
+ results.select { |result| result.failed? }
20
+ end
21
+
22
+ def success
23
+ results.select { |result| result.success? }
24
+ end
25
+
26
+ def has_errors?
27
+ !failed.empty?
28
+ end
29
+
30
+ private
31
+
32
+ def validate_result(result)
33
+ result.respond_to?(:failed?) &&
34
+ result.respond_to?(:success?)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,153 @@
1
+ require 'rest_client'
2
+ require 'chef/sandbox'
3
+ require 'chef/config'
4
+
5
+ module Berkshelf
6
+ # @author Jamie Winsor <jamie@vialstudios.com>
7
+ class Uploader
8
+ attr_reader :cookbook_store
9
+ attr_reader :server_url
10
+ attr_reader :queue
11
+
12
+ # @param [Berkshelf::CookbookStore] cookbook_store
13
+ # the CookbookStore containing the Cookbooks you with to upload
14
+ # @param [String] server_url
15
+ # the URL to the Chef Server to upload Cookbooks to
16
+ # @param [Hash] options
17
+ # a hash of options
18
+ #
19
+ # Options:
20
+ # node_name: the name of the client used to sign REST requests to
21
+ # the Chef Server.
22
+ #
23
+ # Default: the value of Chef::Config[:node_name]
24
+ #
25
+ # client_key: the filepath location for the client's key used to sign
26
+ # REST requests to the Chef Server.
27
+ #
28
+ # Default: the value of Chef::Config[:client_key]
29
+ def initialize(cookbook_store, server_url, options = {})
30
+ options[:node_name] ||= Chef::Config[:node_name]
31
+ options[:client_key] ||= Chef::Config[:client_key]
32
+
33
+ @cookbook_store = cookbook_store
34
+ @server_url = server_url
35
+ @rest = Chef::REST.new(server_url, options[:node_name], options[:client_key])
36
+ @queue = []
37
+ end
38
+
39
+ # Uploads the given CookbookSource to the given Chef server url.
40
+ #
41
+ # @param [String] name
42
+ # name of the Cookbook to upload
43
+ # @param [String] version
44
+ # version of the Cookbook to upload
45
+ # @param [Hash] options
46
+ # a hash of options
47
+ #
48
+ # Options:
49
+ # force: Upload the Cookbook even if the version already exists and is
50
+ # frozen on the target Chef Server
51
+ # freeze: Freeze the uploaded Cookbook on the Chef Server so that it
52
+ # cannot be overwritten
53
+ #
54
+ # @return [TXResult]
55
+ def upload(name, version, options = {})
56
+ upload!(name, version, options)
57
+ rescue BerkshelfError => e
58
+ TXResult.new(:error, e.message)
59
+ end
60
+
61
+ # See #upload. This function will raise if an error occurs.
62
+ def upload!(name, version, options = {})
63
+ cookbook = cookbook_store.cookbook(name, version)
64
+ raise UploadFailure, "Source not downloaded" if cookbook.nil?
65
+
66
+ cookbook.validate!
67
+
68
+ checksums = cookbook.checksums.dup
69
+ new_sandbox = create_sandbox(checksums)
70
+ upload_checksums_to_sandbox(checksums, new_sandbox)
71
+ commit_sandbox(new_sandbox)
72
+ save_cookbook(cookbook, options)
73
+
74
+ TXResult.new(:ok, "#{name} (#{version}) uploaded to: #{server_url}")
75
+ end
76
+
77
+ private
78
+
79
+ attr_reader :rest
80
+
81
+ def create_sandbox(checksums)
82
+ massaged_sums = checksums.inject({}) do |memo, elt|
83
+ memo[elt.first] = nil
84
+ memo
85
+ end
86
+
87
+ rest.post_rest("sandboxes", :checksums => massaged_sums)
88
+ end
89
+
90
+ def commit_sandbox(sandbox)
91
+ # Retry if S3 is claims a checksum doesn't exist (the eventual
92
+ # in eventual consistency)
93
+ retries = 0
94
+ begin
95
+ rest.put_rest(sandbox['uri'], is_completed: true)
96
+ rescue Net::HTTPServerException => e
97
+ if e.message =~ /^400/ && (retries += 1) <= 5
98
+ sleep 2
99
+ retry
100
+ else
101
+ raise
102
+ end
103
+ end
104
+ end
105
+
106
+ def upload_checksums_to_sandbox(checksums, sandbox)
107
+ sandbox['checksums'].each do |checksum, info|
108
+ if info['needs_upload'] == true
109
+ # JW TODO: threads, fibers, or evented uploads here
110
+ upload_file(checksums[checksum], checksum, info['url'])
111
+ end
112
+ end
113
+ end
114
+
115
+ def upload_file(file, checksum, url)
116
+ # Checksum is the hexadecimal representation of the md5, but we
117
+ # need the base64 encoding for the content-md5 header
118
+ checksum64 = Base64.encode64([checksum].pack("H*")).strip
119
+ timestamp = Time.now.utc.iso8601
120
+ file_contents = File.open(file, "rb") {|f| f.read}
121
+
122
+ sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(
123
+ :http_method => :put,
124
+ :path => URI.parse(url).path,
125
+ :body => file_contents,
126
+ :timestamp => timestamp,
127
+ :user_id => rest.client_name
128
+ )
129
+ headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' }
130
+ headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key)))
131
+
132
+ begin
133
+ RestClient::Resource.new(url, headers: headers, timeout: 1800, open_timeout: 1800).put(file_contents)
134
+ rescue RestClient::Exception => e
135
+ raise Berkshelf::UploadFailure, "Failed to upload 'file' to 'url': #{e.message}\n#{e.response.body}"
136
+ end
137
+ end
138
+
139
+ def save_cookbook(cookbook, options = {})
140
+ options[:freeze] ||= false
141
+ options[:force] ||= false
142
+
143
+ url = "cookbooks/#{cookbook.cookbook_name}/#{cookbook.version}"
144
+ url << "?force=true" if options[:force]
145
+
146
+ rest.put_rest(url, cookbook)
147
+ end
148
+
149
+ def validate_source(source)
150
+ source.is_a?(Berkshelf::CookbookSource)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,3 @@
1
+ module Berkshelf
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,29 @@
1
+ require 'chef/knife'
2
+
3
+ module Berkshelf
4
+ class BerksInit < Chef::Knife
5
+ deps do
6
+ require 'berkshelf'
7
+ end
8
+
9
+ banner "knife berks init [PATH]"
10
+
11
+ def run
12
+ ::Berkshelf.ui = ui
13
+ config[:path] = File.expand_path(@name_args.first || Dir.pwd)
14
+
15
+ if File.chef_cookbook?(config[:path])
16
+ config[:chefignore] = true
17
+ config[:metadata_entry] = true
18
+ end
19
+
20
+ generator = ::Berkshelf::InitGenerator.new([], config)
21
+ generator.invoke_all
22
+
23
+ ::Berkshelf.ui.info "Successfully initialized"
24
+ rescue BerkshelfError => e
25
+ Berkshelf.ui.fatal e
26
+ exit e.status_code
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ require 'chef/knife'
2
+
3
+ module Berkshelf
4
+ class BerksInstall < Chef::Knife
5
+ deps do
6
+ require 'berkshelf'
7
+ end
8
+
9
+ banner "knife berks install (options)"
10
+
11
+ option :without,
12
+ :short => "-W WITHOUT",
13
+ :long => "--without WITHOUT",
14
+ :description => "Exclude cookbooks that are in these groups",
15
+ :proc => lambda { |w| w.split(",") },
16
+ :default => Array.new
17
+
18
+ def run
19
+ ::Berkshelf.ui = ui
20
+ cookbook_file = ::Berkshelf::Berksfile.from_file(File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME))
21
+ cookbook_file.install(config)
22
+ rescue BerkshelfError => e
23
+ Berkshelf.ui.fatal e
24
+ exit e.status_code
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ require 'chef/knife'
2
+
3
+ module Berkshelf
4
+ class BerksUpdate < BerksInstall
5
+ deps do
6
+ require 'berkshelf'
7
+ end
8
+
9
+ banner "knife berks update"
10
+
11
+ alias :install_run :run
12
+
13
+ def run
14
+ ::Berkshelf.ui = ui
15
+
16
+ Lockfile.remove!
17
+ install_run
18
+ rescue BerkshelfError => e
19
+ Berkshelf.ui.fatal e
20
+ exit e.status_code
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef/knife'
2
+
3
+ module Berkshelf
4
+ class BerksUpload < Chef::Knife
5
+ deps do
6
+ require 'berkshelf'
7
+ end
8
+
9
+ banner "knife berks upload (options)"
10
+
11
+ option :without,
12
+ :short => "-W WITHOUT",
13
+ :long => "--without WITHOUT",
14
+ :description => "Exclude cookbooks that are in these groups",
15
+ :proc => lambda { |w| w.split(",") },
16
+ :default => Array.new
17
+
18
+ option :freeze,
19
+ :long => "--freeze",
20
+ :description => "Freeze the uploaded cookbooks so that they cannot be overwritten",
21
+ :boolean => true,
22
+ :default => false
23
+
24
+ option :force,
25
+ :long => "--force",
26
+ :description => "Upload all cookbooks even if a frozen one exists on the target Chef Server",
27
+ :boolean => true,
28
+ :default => false
29
+
30
+ def run
31
+ ::Berkshelf.ui = ui
32
+ cookbook_file = ::Berkshelf::Berksfile.from_file(File.join(Dir.pwd, "Berksfile"))
33
+ cookbook_file.upload(Chef::Config[:chef_server_url], config)
34
+ rescue BerkshelfError => e
35
+ Berkshelf.ui.fatal e
36
+ exit e.status_code
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ cookbook 'ntp', '<= 1.0.0'
2
+ cookbook 'mysql'
3
+ cookbook 'nginx', '< 0.101.2'
@@ -0,0 +1,12 @@
1
+ Description
2
+ ===========
3
+
4
+ Requirements
5
+ ============
6
+
7
+ Attributes
8
+ ==========
9
+
10
+ Usage
11
+ =====
12
+
@@ -0,0 +1,6 @@
1
+ maintainer "Josiah Kiehl"
2
+ maintainer_email "josiah@skirmisher.net"
3
+ license "DO WHAT YOU WANT CAUSE A PIRATE IS FREE"
4
+ description "Installs/Configures example_cookbook"
5
+ long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
6
+ version "0.5.0"
@@ -0,0 +1,8 @@
1
+ #
2
+ # Cookbook Name:: example_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2012, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
@@ -0,0 +1,12 @@
1
+ Description
2
+ ===========
3
+
4
+ Requirements
5
+ ============
6
+
7
+ Attributes
8
+ ==========
9
+
10
+ Usage
11
+ =====
12
+
@@ -0,0 +1,6 @@
1
+ maintainer "Josiah Kiehl"
2
+ maintainer_email "josiah@skirmisher.net"
3
+ license "DO WHAT YOU WANT CAUSE A PIRATE IS FREE"
4
+ description "Installs/Configures example_cookbook"
5
+ long_description IO.read(File.join(File.dirname(__FILE__), 'README.md'))
6
+ version "0.5.0"
@@ -0,0 +1,8 @@
1
+ #
2
+ # Cookbook Name:: example_cookbook
3
+ # Recipe:: default
4
+ #
5
+ # Copyright 2012, YOUR_COMPANY_NAME
6
+ #
7
+ # All rights reserved - Do Not Redistribute
8
+ #
@@ -0,0 +1 @@
1
+ (*&)*^^^@(8123_)@(jkladsf}})end))) +++ /0
@@ -0,0 +1 @@
1
+ <%= (*&)*^^^@(8123_)@(jkladsf}})end))) +++%>
@@ -0,0 +1,77 @@
1
+ Description
2
+ ===========
3
+
4
+ Installs nginx from package OR source code and sets up configuration handling similar to Debian's Apache2 scripts.
5
+
6
+ Requirements
7
+ ============
8
+
9
+ Cookbooks
10
+ ---------
11
+
12
+ * build-essential (for nginx::source)
13
+ * runit (for nginx::source)
14
+
15
+ Platform
16
+ --------
17
+
18
+ Debian or Ubuntu though may work where 'build-essential' works, but other platforms are untested.
19
+
20
+ Attributes
21
+ ==========
22
+
23
+ All node attributes are set under the `nginx` namespace.
24
+
25
+ * version - sets the version to install.
26
+ * dir - configuration dir.
27
+ * `log_dir` - where logs go.
28
+ * user - user to run as.
29
+ * binary - path to nginx binary.
30
+ * gzip - all attributes under the `gzip` namespace configure the gzip module.
31
+ * keepalive - whether to use keepalive.
32
+ * `keepalive_timeout` - set the keepalive timeout.
33
+ * `worker_processes` - number of workers to spawn.
34
+ * `worker_connections` - number of connections per worker.
35
+ * `server_names_hash_bucket_size`
36
+ * `url` - URL where to download the nginx source tarball
37
+
38
+ The following attributes are set at the 'normal' node level via the `nginx::source` recipe.
39
+
40
+ * `install_path` - for nginx::source, sets the --prefix installation.
41
+ * `src_binary` - for nginx::source, sets the binary location.
42
+ * `configure_flags` - for nginx::source, an array of flags to use for compilation.
43
+
44
+ Usage
45
+ =====
46
+
47
+ Provides two ways to install and configure nginx.
48
+
49
+ * Install via native package (nginx::default)
50
+ * Install via compiled source (nginx::source)
51
+
52
+ Both recipes implement configuration handling similar to the Debian Apache2 site enable/disable.
53
+
54
+ There's some redundancy in that the config handling hasn't been separated from the installation method (yet), so use only one of the recipes.
55
+
56
+ Some of the attributes mentioned above are only set in the `nginx::source` recipe. They can be overridden by setting them via a role in `override_attributes`.
57
+
58
+ License and Author
59
+ ==================
60
+
61
+ Author:: Joshua Timberman (<joshua@opscode.com>)
62
+ Author:: Adam Jacob (<adam@opscode.com>)
63
+ Author:: AJ Christensen (<aj@opscode.com>)
64
+
65
+ Copyright:: 2008-2011, Opscode, Inc
66
+
67
+ Licensed under the Apache License, Version 2.0 (the "License");
68
+ you may not use this file except in compliance with the License.
69
+ You may obtain a copy of the License at
70
+
71
+ http://www.apache.org/licenses/LICENSE-2.0
72
+
73
+ Unless required by applicable law or agreed to in writing, software
74
+ distributed under the License is distributed on an "AS IS" BASIS,
75
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
76
+ See the License for the specific language governing permissions and
77
+ limitations under the License.