berkshelf-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +17 -0
  5. data/CONTRIBUTING.md +33 -0
  6. data/Gemfile +40 -0
  7. data/Guardfile +20 -0
  8. data/LICENSE +201 -0
  9. data/README.md +37 -0
  10. data/Thorfile +39 -0
  11. data/berkshelf-api.gemspec +35 -0
  12. data/bin/berks-api +5 -0
  13. data/lib/berkshelf-api.rb +1 -0
  14. data/lib/berkshelf/api.rb +25 -0
  15. data/lib/berkshelf/api/application.rb +114 -0
  16. data/lib/berkshelf/api/cache_builder.rb +60 -0
  17. data/lib/berkshelf/api/cache_builder/worker.rb +116 -0
  18. data/lib/berkshelf/api/cache_builder/worker/chef_server.rb +46 -0
  19. data/lib/berkshelf/api/cache_builder/worker/opscode.rb +59 -0
  20. data/lib/berkshelf/api/cache_manager.rb +96 -0
  21. data/lib/berkshelf/api/config.rb +23 -0
  22. data/lib/berkshelf/api/cucumber.rb +11 -0
  23. data/lib/berkshelf/api/dependency_cache.rb +123 -0
  24. data/lib/berkshelf/api/endpoint.rb +17 -0
  25. data/lib/berkshelf/api/endpoint/v1.rb +19 -0
  26. data/lib/berkshelf/api/errors.rb +8 -0
  27. data/lib/berkshelf/api/generic_server.rb +50 -0
  28. data/lib/berkshelf/api/logging.rb +37 -0
  29. data/lib/berkshelf/api/mixin.rb +7 -0
  30. data/lib/berkshelf/api/mixin/services.rb +48 -0
  31. data/lib/berkshelf/api/rack_app.rb +5 -0
  32. data/lib/berkshelf/api/remote_cookbook.rb +3 -0
  33. data/lib/berkshelf/api/rest_gateway.rb +62 -0
  34. data/lib/berkshelf/api/rspec.rb +20 -0
  35. data/lib/berkshelf/api/rspec/server.rb +29 -0
  36. data/lib/berkshelf/api/site_connector.rb +7 -0
  37. data/lib/berkshelf/api/site_connector/opscode.rb +162 -0
  38. data/lib/berkshelf/api/srv_ctl.rb +63 -0
  39. data/lib/berkshelf/api/version.rb +5 -0
  40. data/spec/fixtures/reset.pem +27 -0
  41. data/spec/spec_helper.rb +53 -0
  42. data/spec/support/actor_mocking.rb +7 -0
  43. data/spec/support/chef_server.rb +73 -0
  44. data/spec/unit/berkshelf/api/application_spec.rb +24 -0
  45. data/spec/unit/berkshelf/api/cache_builder/worker/chef_server_spec.rb +59 -0
  46. data/spec/unit/berkshelf/api/cache_builder/worker/opscode_spec.rb +41 -0
  47. data/spec/unit/berkshelf/api/cache_builder/worker_spec.rb +80 -0
  48. data/spec/unit/berkshelf/api/cache_builder_spec.rb +37 -0
  49. data/spec/unit/berkshelf/api/cache_manager_spec.rb +123 -0
  50. data/spec/unit/berkshelf/api/config_spec.rb +24 -0
  51. data/spec/unit/berkshelf/api/dependency_cache_spec.rb +109 -0
  52. data/spec/unit/berkshelf/api/endpoint/v1_spec.rb +18 -0
  53. data/spec/unit/berkshelf/api/logging_spec.rb +28 -0
  54. data/spec/unit/berkshelf/api/mixin/services_spec.rb +68 -0
  55. data/spec/unit/berkshelf/api/rack_app_spec.rb +6 -0
  56. data/spec/unit/berkshelf/api/rest_gateway_spec.rb +26 -0
  57. data/spec/unit/berkshelf/api/site_connector/opscode_spec.rb +85 -0
  58. data/spec/unit/berkshelf/api/srv_ctl_spec.rb +56 -0
  59. metadata +293 -0
@@ -0,0 +1,29 @@
1
+ module Berkshelf::API::RSpec
2
+ module Server
3
+ class << self
4
+ include Berkshelf::API::Mixin::Services
5
+
6
+ def clear_cache
7
+ cache_manager.clear
8
+ end
9
+
10
+ def instance
11
+ Berkshelf::API::Application.instance
12
+ end
13
+
14
+ def running?
15
+ Berkshelf::API::Application.running?
16
+ end
17
+
18
+ def start(options = {})
19
+ options = options.reverse_merge(port: 26210, log_location: "/dev/null", endpoints: [])
20
+ Berkshelf::API::Application.config.endpoints = options[:endpoints]
21
+ Berkshelf::API::Application.run!(options) unless running?
22
+ end
23
+
24
+ def stop
25
+ Berkshelf::API::Application.shutdown
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ module Berkshelf
2
+ module API
3
+ module SiteConnector
4
+ require_relative 'site_connector/opscode'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,162 @@
1
+ require 'open-uri'
2
+ require 'retryable'
3
+ require 'archive'
4
+ require 'tempfile'
5
+
6
+ module Berkshelf::API
7
+ module SiteConnector
8
+ class Opscode
9
+ class << self
10
+ # @param [String] version
11
+ #
12
+ # @return [String]
13
+ def uri_escape_version(version)
14
+ version.to_s.gsub('.', '_')
15
+ end
16
+
17
+ # @param [String] uri
18
+ #
19
+ # @return [String]
20
+ def version_from_uri(uri)
21
+ File.basename(uri.to_s).gsub('_', '.')
22
+ end
23
+ end
24
+
25
+ include Celluloid
26
+ include Berkshelf::API::Logging
27
+
28
+ V1_API = 'http://cookbooks.opscode.com/api/v1'.freeze
29
+
30
+ # @return [String]
31
+ attr_reader :api_uri
32
+ # @return [Integer]
33
+ # how many retries to attempt on HTTP requests
34
+ attr_reader :retries
35
+ # @return [Float]
36
+ # time to wait between retries
37
+ attr_reader :retry_interval
38
+
39
+ # @param [Faraday::Connection] connection
40
+ # Optional parameter for setting the connection object
41
+ # This should only be set manually for testing
42
+ def initialize(uri = V1_API, options = {})
43
+ options = { retries: 5, retry_interval: 0.5 }.merge(options)
44
+ @api_uri = uri
45
+
46
+ @connection = Faraday.new(uri) do |c|
47
+ c.response :parse_json
48
+ c.use Faraday::Adapter::NetHttp
49
+ end
50
+ end
51
+
52
+ # @return [Array<String>]
53
+ # A list of cookbook names available on the server
54
+ def cookbooks
55
+ start = 0
56
+ count = connection.get("cookbooks").body["total"]
57
+ cookbooks = Array.new
58
+
59
+ while count > 0
60
+ cookbooks += connection.get("cookbooks?start=#{start}&items=#{count}").body["items"]
61
+ start += 100
62
+ count -= 100
63
+ end
64
+
65
+ cookbooks.map { |cb| cb["cookbook_name"] }
66
+ end
67
+
68
+ # @param [String] cookbook
69
+ # the name of the cookbook to find version for
70
+ #
71
+ # @return [Array<String>]
72
+ # A list of versions of this cookbook available on the server
73
+ def versions(cookbook)
74
+ response = connection.get("cookbooks/#{cookbook}")
75
+
76
+ case response.status
77
+ when (200..299)
78
+ response.body['versions'].collect do |version_uri|
79
+ self.class.version_from_uri(version_uri)
80
+ end
81
+ else
82
+ Array.new
83
+ end
84
+ end
85
+
86
+ # @param [String] cookbook
87
+ # The name of the cookbook to download
88
+ # @param [String] version
89
+ # The version of the cookbook to download
90
+ # @param [String] destination
91
+ # The directory to download the cookbook to
92
+ #
93
+ # @return [String, nil]
94
+ def download(name, version, destination = Dir.mktmpdir)
95
+ log.info "downloading #{name}(#{version})"
96
+ if uri = download_uri(name, version)
97
+ archive = stream(uri)
98
+ Archive.extract(archive.path, destination)
99
+ end
100
+ ensure
101
+ archive.unlink unless archive.nil?
102
+ end
103
+
104
+ # Return the location where a cookbook of the given name and version can be downloaded from
105
+ #
106
+ # @param [String] cookbook
107
+ # The name of the cookbook
108
+ # @param [String] version
109
+ # The version of the cookbook
110
+ #
111
+ # @return [String, nil]
112
+ def download_uri(name, version)
113
+ unless cookbook = find(name, version)
114
+ return nil
115
+ end
116
+ cookbook[:file]
117
+ end
118
+
119
+ # @param [String] cookbook
120
+ # The name of the cookbook to retrieve
121
+ # @param [String] version
122
+ # The version of the cookbook to retrieve
123
+ #
124
+ # @return [Hashie::Mash, nil]
125
+ def find(name, version)
126
+ response = connection.get("cookbooks/#{name}/versions/#{self.class.uri_escape_version(version)}")
127
+
128
+ case response.status
129
+ when (200..299)
130
+ response.body
131
+ else
132
+ nil
133
+ end
134
+ end
135
+
136
+ # Stream the response body of a remote URL to a file on the local file system
137
+ #
138
+ # @param [String] target
139
+ # a URL to stream the response body from
140
+ #
141
+ # @return [Tempfile]
142
+ def stream(target)
143
+ local = Tempfile.new('opscode-site-stream')
144
+ local.binmode
145
+
146
+ retryable(tries: retries, on: OpenURI::HTTPError, sleep: retry_interval) do
147
+ open(target, 'rb', connection.headers) do |remote|
148
+ local.write(remote.read)
149
+ end
150
+ end
151
+
152
+ local
153
+ ensure
154
+ local.close(false) unless local.nil?
155
+ end
156
+
157
+ private
158
+
159
+ attr_reader :connection
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,63 @@
1
+ require 'optparse'
2
+ require 'buff/extensions'
3
+
4
+ module Berkshelf
5
+ module API
6
+ class SrvCtl
7
+ class << self
8
+ # @param [Array] args
9
+ #
10
+ # @return [Hash]
11
+ def parse_options(args, filename)
12
+ options = Hash.new
13
+
14
+ OptionParser.new("Usage: #{filename} [options]") do |opts|
15
+ opts.on("-p", "--port PORT", Integer, "set the listening port") do |v|
16
+ options[:port] = v
17
+ end
18
+
19
+ opts.on("-v", "--verbose", "run with verbose output") do
20
+ options[:log_level] = "INFO"
21
+ end
22
+
23
+ opts.on("-d", "--debug", "run with debug output") do
24
+ options[:log_level] = "DEBUG"
25
+ end
26
+
27
+ opts.on("-q", "--quiet", "silence output") do
28
+ options[:log_location] = '/dev/null'
29
+ end
30
+
31
+ opts.on_tail("-h", "--help", "show this message") do
32
+ puts opts
33
+ exit
34
+ end
35
+ end.parse!(args)
36
+
37
+ options.symbolize_keys
38
+ end
39
+
40
+ # @param [Array] args
41
+ # @param [String] filename
42
+ def run(args, filename)
43
+ options = parse_options(args, filename)
44
+ new(options).start
45
+ end
46
+ end
47
+
48
+ attr_reader :options
49
+
50
+ # @param [Hash] options
51
+ # @see {Berkshelf::API::Application.run} for the list of valid options
52
+ def initialize(options = {})
53
+ @options = options
54
+ @options[:eager_build] = true
55
+ end
56
+
57
+ def start
58
+ require 'berkshelf/api'
59
+ Berkshelf::API::Application.run(options)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,5 @@
1
+ module Berkshelf
2
+ module API
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpQIBAAKCAQEAyyUMqrTh1IzKOyE0fvXEWC7m0AdMI8/dr9JJMUKtK9vhhP0w
3
+ rm6m95GoybFM2IRryukFsAxpcir3M1ungTU3Smq4MshhMJ7H9FbvZVfQoknTbCsR
4
+ w6scg2fBepxT2+fcGRufr8nAh92M3uUkN9bMMTAkt18D4br6035YvdmvHDJERxYq
5
+ ByA/720AdI9VNSIvw+x8oqsIkXLEdF6dgT9MpG5iWZT66pbFsnNZpRrd4/bFNWBY
6
+ +13aOqdmjiTL08/EdgQFKMT5qimpos1TuQhA7mwInOjQgzVu9uCDkMiYejaLbUz0
7
+ lGyS8y4uxu6z2hA900Jg/z+JJuXymH5QAX3GZQIDAQABAoIBAQCtFXkwbYPI1Nht
8
+ /wG6du5+8B9K+hy+mppY9wPTy+q+Zs9Ev3Fd/fuXDm1QxBckl9c8AMUO1dR2KPOM
9
+ t7gFl/DvH/SnmCFvCqp1nijFIUgrLlnMXPn6zG0z7RBlxpKQ2IGohufNIEpBuNwR
10
+ Ag2U4hgChPGTp4ooJ2cVEh7MS5AupYPDbC62dWEdW68aRTWhh2BCGAWBb6s16yl9
11
+ aZ7+OcxW2eeRJVbRfLkLQEDutJZi5TfOEn5QPc86ZgxcCmnvwulnpnhpz6QCkgQt
12
+ OP/+KRqDhWSDVCFREVT30fUIj1EWvK7NFWASZQxueZStuIvMEKeFebYfrbHxRFzJ
13
+ UmaxJnWVAoGBAPbKLpeky6ClccBaHHrCgjzakoDfGgyNKDQ9g753lJxB8nn7d9X4
14
+ HQpkWpfqAGFRZp1hI2H+VxyUXLh2Ob5OUeTm0OZJll35vycOaQEtfgIScXTcvzn0
15
+ 16J9eX2YY4wIHEEMh85nKk8BEGgiNP5nuEviHocCeYXoi/Zq3+qj6v63AoGBANK5
16
+ 4nyi6LBQFs1CUc7Sh7vjtOE3ia7KeRmOr7gS6QhS3iK3Oa8FzBLJ6ETjN2a9Bw8N
17
+ cF7I/+cr4s7DUJjxdb53D/J6TVSYORNNCUVnpF/uB2LqqdXDYmpO0PvFkXFoYTnJ
18
+ kaLAN8uCoLKr6JH9tq3DfXIfDIHiZ+BOIvI070fDAoGBAMDyzEDFmGruTyRLj66u
19
+ +rJnVVmqlKwxhLhrS+CTj74nlVOnt0a0KMhiM65IRqnPwcHUG5zXBPaUTHXwAS93
20
+ /nFPwQ37hLPOupPnoVNJZRZrowbyPBQtCJbDMURv64ylHqoBCQDoCd0hANnZvMMX
21
+ BrFVhfaaibaXXS542r6SD/27AoGAECadHE5kJTdOOBcwK/jo3Fa8g1J9Y/8yvum3
22
+ wBT69V9clS6T5j08geglvDnqAh7UzquKBEnFi1NKw+wmXkKLcrivaTdEfApavYb3
23
+ AfHKoGue907jC3Y5Mcquq81ds2J7qTEwz1eKLzfo1yjj32ShvrmwALIuhDn1GjUC
24
+ 6qtx938CgYEApEqvu0nocR1jmVVlLe5uKQBj949dh6NGq0R5Lztz6xufaTYzMC3d
25
+ AZG9XPPjRqSLs+ylSXJpwHEwoeyLFDaJcO+GgW1/ut4MC2HppOx6aImwDdXMHUWR
26
+ KYGIFF4AU/IYoBcanAm4s078EH/Oz01B2c7tR2TqabisPgLYe7PXSCw=
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'rspec'
4
+ require 'spork'
5
+ require 'rack/test'
6
+
7
+ Spork.prefork do
8
+ Dir[File.join(File.expand_path("../../spec/support/**/*.rb", __FILE__))].each { |f| require f }
9
+
10
+ RSpec.configure do |config|
11
+ config.include Berkshelf::RSpec::ChefServer
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+
17
+ config.mock_with :rspec
18
+ config.treat_symbols_as_metadata_keys_with_true_values = true
19
+ config.filter_run focus: true
20
+ config.run_all_when_everything_filtered = true
21
+
22
+ config.before(:suite) { Berkshelf::RSpec::ChefServer.start }
23
+ config.before(:all) { Berkshelf::API::Logging.init(location: '/dev/null') }
24
+
25
+ config.before do
26
+ Celluloid.shutdown
27
+ Celluloid.boot
28
+ Berkshelf::API::CacheManager.cache_file = tmp_path.join('cerch').to_s
29
+ clean_tmp_path
30
+ end
31
+ end
32
+
33
+ def app_root_path
34
+ Pathname.new(File.expand_path('../../', __FILE__))
35
+ end
36
+
37
+ def tmp_path
38
+ app_root_path.join('spec/tmp')
39
+ end
40
+
41
+ def fixtures_path
42
+ app_root_path.join('spec/fixtures')
43
+ end
44
+
45
+ def clean_tmp_path
46
+ FileUtils.rm_rf(tmp_path)
47
+ FileUtils.mkdir_p(tmp_path)
48
+ end
49
+ end
50
+
51
+ Spork.each_run do
52
+ require 'berkshelf/api'
53
+ end
@@ -0,0 +1,7 @@
1
+ RSpec.configuration.before(:each) do
2
+ class Celluloid::ActorProxy
3
+ [ :should_receive, :should_not_receive, :stub, :stub_chain, :should, :should_not ].each do |method|
4
+ undef_method(method) if method_defined?(method)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,73 @@
1
+ require 'chef_zero/server'
2
+
3
+ module Berkshelf::RSpec
4
+ module ChefServer
5
+ class << self
6
+ def clear_request_log
7
+ @request_log = Array.new
8
+ end
9
+
10
+ def request_log
11
+ @request_log ||= Array.new
12
+ end
13
+
14
+ def server
15
+ @server ||= ChefZero::Server.new(port: PORT, generate_real_keys: false)
16
+ end
17
+
18
+ def server_url
19
+ (@server && @server.url) || "http://localhost/#{PORT}"
20
+ end
21
+
22
+ def start
23
+ server.start_background
24
+ server.on_response do |request, response|
25
+ request_log << [ request, response ]
26
+ end
27
+ clear_request_log
28
+
29
+ server
30
+ end
31
+
32
+ def stop
33
+ @server.stop if @server
34
+ end
35
+
36
+ def running?
37
+ @server && @server.running?
38
+ end
39
+ end
40
+
41
+ PORT = 8889
42
+
43
+ def chef_client(name, hash = Hash.new)
44
+ load_data(:clients, name, hash)
45
+ end
46
+
47
+ def chef_cookbook(name, version, cookbook = Hash.new)
48
+ ChefServer.server.load_data("cookbooks" => { "#{name}-#{version}" => cookbook })
49
+ end
50
+
51
+ def chef_data_bag(name, hash = Hash.new)
52
+ ChefServer.server.load_data({ 'data' => { name => hash }})
53
+ end
54
+
55
+ def chef_environment(name, hash = Hash.new)
56
+ load_data(:environments, name, hash)
57
+ end
58
+
59
+ def chef_node(name, hash = Hash.new)
60
+ load_data(:nodes, name, hash)
61
+ end
62
+
63
+ def chef_role(name, hash = Hash.new)
64
+ load_data(:roles, name, hash)
65
+ end
66
+
67
+ private
68
+
69
+ def load_data(key, name, hash)
70
+ ChefServer.server.load_data(key.to_s => { name => JSON.fast_generate(hash) })
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Berkshelf::API::Application do
4
+ describe "ClassMethods" do
5
+ subject { described_class }
6
+
7
+ its(:registry) { should be_a(Celluloid::Registry) }
8
+
9
+ describe "::run!" do
10
+ include Berkshelf::API::Mixin::Services
11
+
12
+ let(:options) { { log_location: '/dev/null' } }
13
+ subject(:run) { described_class.run!(options) }
14
+
15
+ context "when given true for :disable_http" do
16
+ it "does not start the REST Gateway" do
17
+ options[:disable_http] = true
18
+ run
19
+ expect { rest_gateway }.to raise_error(Berkshelf::API::NotStartedError)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end