berkshelf-api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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