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,23 @@
1
+ require 'buff/config/json'
2
+
3
+ module Berkshelf::API
4
+ class Config < Buff::Config::JSON
5
+ class << self
6
+ # @return [String]
7
+ def default_path
8
+ File.expand_path("~/.berkshelf/api-server/config.json")
9
+ end
10
+ end
11
+
12
+ attribute 'endpoints',
13
+ type: Array,
14
+ default: [
15
+ {
16
+ type: "opscode",
17
+ options: {
18
+ url: 'http://cookbooks.opscode.com/api/v1'
19
+ }
20
+ }
21
+ ]
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'rspec'
2
+
3
+ World(Berkshelf::API::RSpec)
4
+
5
+ Given(/^the Berkshelf API server cache is up to date$/) do
6
+ cache_builder.build
7
+ end
8
+
9
+ Given(/^the Berkshelf API server's cache is empty$/) do
10
+ cache_manager.clear
11
+ end
@@ -0,0 +1,123 @@
1
+ module Berkshelf::API
2
+ # @note
3
+ # DependencyCache stores its data internally as a Mash
4
+ # The structure is as follows
5
+ #
6
+ # {
7
+ # "cookbook_name" => {
8
+ # "x.y.z" => {
9
+ # :dependencies => { "cookbook_name" => "constraint" },
10
+ # :platforms => { "platform" => "constraint" }
11
+ # }
12
+ # }
13
+ # }
14
+ class DependencyCache
15
+ class << self
16
+ # Read an archived cache and re-instantiate it
17
+ #
18
+ # @param [#to_s] filepath
19
+ # path to an archived cache
20
+ #
21
+ # @raise [Berkshelf::API::SaveNotFoundError]
22
+ # @raise [Berkshelf::API::InvalidSaveError]
23
+ #
24
+ # @return [DependencyCache]
25
+ def from_file(filepath)
26
+ contents = JSON.parse(File.read(filepath.to_s))
27
+ new(contents)
28
+ rescue Errno::ENOENT => ex
29
+ raise SaveNotFoundError.new(ex)
30
+ rescue JSON::ParserError => ex
31
+ raise InvalidSaveError.new(ex)
32
+ end
33
+ end
34
+
35
+ extend Forwardable
36
+ def_delegators :@cache, :[], :[]=
37
+
38
+ # @param [Hash] contents
39
+ def initialize(contents = {})
40
+ @cache = Hash[contents]
41
+ end
42
+
43
+ # @param [RemoteCookbook] cookbook
44
+ # @param [Ridley::Chef::Cookbook::Metadata] metadata
45
+ #
46
+ # @return [Hash]
47
+ def add(cookbook, metadata)
48
+ platforms = metadata.platforms || Hash.new
49
+ dependencies = metadata.dependencies || Hash.new
50
+ @cache[cookbook.name.to_s] ||= Hash.new
51
+ @cache[cookbook.name.to_s][cookbook.version.to_s] = {
52
+ platforms: platforms,
53
+ dependencies: dependencies,
54
+ location_type: cookbook.location_type,
55
+ location_path: cookbook.location_path
56
+ }
57
+ end
58
+
59
+ # Clear any items added to this instance
60
+ #
61
+ # @return [Hash]
62
+ def clear
63
+ @cache.clear
64
+ end
65
+
66
+ # Check if the cache knows about the given cookbook version
67
+ #
68
+ # @param [#to_s] name
69
+ # @param [#to_s] version
70
+ #
71
+ # @return [Boolean]
72
+ def has_cookbook?(name, version)
73
+ unless cookbook = @cache[name.to_s]
74
+ return false
75
+ end
76
+
77
+ cookbook.has_key?(version.to_s)
78
+ end
79
+
80
+ # @param [String] name
81
+ # @param [String] version
82
+ #
83
+ # @return [Hash]
84
+ def remove(name, version)
85
+ @cache[name.to_s].delete(version.to_s)
86
+ if @cache[name.to_s].empty?
87
+ @cache.delete(name.to_s)
88
+ end
89
+ @cache
90
+ end
91
+
92
+ # @return [Boolean]
93
+ def empty?
94
+ @cache.empty?
95
+ end
96
+
97
+ # @return [Hash]
98
+ def to_hash
99
+ @cache.to_hash
100
+ end
101
+
102
+ # @return [String]
103
+ def to_json(options = {})
104
+ JSON.generate(to_hash, options)
105
+ end
106
+
107
+ # @return [Array<RemoteCookbook>]
108
+ def cookbooks
109
+ [].tap do |remote_cookbooks|
110
+ @cache.each_pair do |name, versions|
111
+ versions.each do |version, metadata|
112
+ remote_cookbooks << RemoteCookbook.new(name, version, metadata[:location_type], metadata[:location_path])
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def save(path)
119
+ FileUtils.mkdir_p(File.dirname(path))
120
+ File.open(path, 'w+') { |f| f.write(self.to_json) }
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,17 @@
1
+ require 'grape'
2
+
3
+ module Berkshelf::API
4
+ module Endpoint
5
+ class Base < Grape::API
6
+ # Force inbound requests to be JSON
7
+ def call(env)
8
+ env['CONTENT_TYPE'] = 'application/json'
9
+ super
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ Dir["#{File.dirname(__FILE__)}/endpoint/*.rb"].sort.each do |path|
16
+ require "berkshelf/api/endpoint/#{File.basename(path, '.rb')}"
17
+ end
@@ -0,0 +1,19 @@
1
+ module Berkshelf::API
2
+ module Endpoint
3
+ class V1 < Endpoint::Base
4
+ helpers Berkshelf::API::Mixin::Services
5
+ version 'v1', using: :header, vendor: 'berkshelf'
6
+ format :json
7
+
8
+ rescue_from Grape::Exceptions::Validation do |e|
9
+ body = JSON.generate({status: e.status, message: e.message, param: e.param})
10
+ rack_response(body, e.status, "Content-type" => "application/json")
11
+ end
12
+
13
+ desc "list all known cookbooks"
14
+ get 'universe' do
15
+ cache_manager.cache
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ module Berkshelf::API
2
+ class APIError < StandardError; end
3
+ # Thrown when an actor was expected to be running but wasn't
4
+ class NotStartedError < APIError; end
5
+ class SaveNotFoundError < APIError; end
6
+ class InvalidSaveError < APIError; end
7
+ class MetadataLoadError < APIError; end
8
+ end
@@ -0,0 +1,50 @@
1
+ module Berkshelf::API
2
+ module GenericServer
3
+ class << self
4
+ def included(base)
5
+ base.send(:include, Celluloid)
6
+ base.send(:extend, ClassMethods)
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ # Returns the currently running instance of the including Class
12
+ #
13
+ # @return [Celluloid::Actor]
14
+ def instance
15
+ unless Application[server_name] && Application[server_name].alive?
16
+ raise NotStartedError, "#{server_name} not running"
17
+ end
18
+ Application[server_name]
19
+ end
20
+
21
+ # Set the name that the actor will be registered as with the applicaiton
22
+ #
23
+ # @param [#to_sym, nil]
24
+ #
25
+ # @return [Symbol]
26
+ def server_name(name = nil)
27
+ return @server_name if name.nil?
28
+ @server_name = name.to_sym
29
+ end
30
+
31
+ # Start the cache manager and add it to the application's registry.
32
+ #
33
+ # @note you probably do not want to manually start the cache manager unless you
34
+ # are testing the application. Start the entire application with {Berkshelf::API::Application.run}
35
+ def start(*args)
36
+ Application[server_name] = new(*args)
37
+ end
38
+
39
+ # Stop the cache manager if it's running.
40
+ #
41
+ # @note you probably don't want to manually stop the cache manager unless you are testing
42
+ # the application. Stop the entire application with {Berkshelf::API::Application.shutdown}
43
+ def stop
44
+ unless actor = Application[server_name]
45
+ actor.terminate
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ module Berkshelf::API
2
+ module Logging
3
+ class << self
4
+ # @return [Logger]
5
+ attr_accessor :logger
6
+
7
+ # @option options [String, Fixnum] :location (STDOUT)
8
+ # @option options [String, nil] :level ("INFO")
9
+ # - "DEBUG
10
+ # - "INFO"
11
+ # - "WARN"
12
+ # - "ERROR"
13
+ # - "FATAL"
14
+ # @option options [Logger::Formatter] :formatter
15
+ #
16
+ # @return [Logger]
17
+ def init(options = {})
18
+ level = options[:level] || "INFO"
19
+ location = options[:location] || STDOUT
20
+ formatter = options[:formatter] || nil
21
+
22
+ Celluloid.logger = @logger = Logger.new(location).tap do |log|
23
+ log.level = Logger::Severity.const_get(level.upcase)
24
+ log.formatter = formatter if formatter
25
+ end
26
+ end
27
+ end
28
+
29
+ init
30
+
31
+ # @return [Logger]
32
+ def logger
33
+ Logging.logger
34
+ end
35
+ alias_method :log, :logger
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module Berkshelf::API
2
+ module Mixin; end
3
+ end
4
+
5
+ Dir["#{File.dirname(__FILE__)}/mixin/*.rb"].sort.each do |path|
6
+ require_relative "mixin/#{File.basename(path, '.rb')}"
7
+ end
@@ -0,0 +1,48 @@
1
+ module Berkshelf::API
2
+ module Mixin
3
+ module Services
4
+ class << self
5
+ def included(base)
6
+ base.extend(ClassMethods)
7
+ base.send(:include, ClassMethods)
8
+ end
9
+
10
+ def extended(base)
11
+ base.send(:include, ClassMethods)
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ # @raise [Berkshelf::API::NotStartedError] if the cache manager has not been started
17
+ #
18
+ # @return [Berkshelf::API::CacheBuilder]
19
+ def cache_builder
20
+ app_actor(:cache_builder)
21
+ end
22
+
23
+ # @raise [Berkshelf::API::NotStartedError] if the cache manager has not been started
24
+ #
25
+ # @return [Berkshelf::API::CacheManager]
26
+ def cache_manager
27
+ app_actor(:cache_manager)
28
+ end
29
+
30
+ # @raise [Berkshelf::API::NotStartedError] if the rest gateway has not been started
31
+ #
32
+ # @return [Berkshelf::API::RESTGateway]
33
+ def rest_gateway
34
+ app_actor(:rest_gateway)
35
+ end
36
+
37
+ private
38
+
39
+ def app_actor(id)
40
+ unless Application[id] && Application[id].alive?
41
+ raise NotStartedError, "#{id} not running"
42
+ end
43
+ Application[id]
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,5 @@
1
+ module Berkshelf::API
2
+ class RackApp < Endpoint::Base
3
+ mount Berkshelf::API::Endpoint::V1
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Berkshelf::API
2
+ class RemoteCookbook < Struct.new(:name, :version, :location_type, :location_path); end
3
+ end
@@ -0,0 +1,62 @@
1
+ require 'reel'
2
+
3
+ module Berkshelf::API
4
+ class RESTGateway < Reel::Server
5
+ extend Forwardable
6
+ include Berkshelf::API::GenericServer
7
+ include Berkshelf::API::Logging
8
+
9
+ DEFAULT_OPTIONS = {
10
+ host: '0.0.0.0',
11
+ port: 26200,
12
+ quiet: false,
13
+ workers: 10
14
+ }.freeze
15
+
16
+ # @return [String]
17
+ attr_reader :host
18
+ # @return [Integer]
19
+ attr_reader :port
20
+ # @return [Integer]
21
+ attr_reader :workers
22
+
23
+ def_delegator :handler, :rack_app
24
+
25
+ server_name :rest_gateway
26
+ finalizer :finalize_callback
27
+
28
+ # @option options [String] :host ('0.0.0.0')
29
+ # @option options [Integer] :port (26100)
30
+ # @option options [Boolean] :quiet (false)
31
+ # @option options [Integer] :workers (10)
32
+ def initialize(options = {})
33
+ options = DEFAULT_OPTIONS.merge(options)
34
+ options[:app] = Berkshelf::API::RackApp.new
35
+
36
+ @host = options[:host]
37
+ @port = options[:port]
38
+ @workers = options[:workers]
39
+ @handler = ::Rack::Handler::Reel.new(options)
40
+ @pool = ::Reel::RackWorker.pool(size: @workers, args: [ @handler ])
41
+
42
+ log.info "REST Gateway listening on #{@host}:#{@port}"
43
+ super(@host, @port, &method(:on_connect))
44
+ end
45
+
46
+ # @param [Reel::Connection] connection
47
+ def on_connect(connection)
48
+ pool.handle(connection.detach)
49
+ end
50
+
51
+ private
52
+
53
+ # @return [Reel::RackWorker]
54
+ attr_reader :pool
55
+ # @return [Rack::Handler::Reel]
56
+ attr_reader :handler
57
+
58
+ def finalize_callback
59
+ pool.terminate if pool && pool.alive?
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ require 'berkshelf/api'
2
+
3
+ module Berkshelf::API
4
+ module RSpec
5
+ require_relative 'rspec/server'
6
+
7
+ include Mixin::Services
8
+
9
+ def berks_dependency(name, version, options = {})
10
+ options[:platforms] ||= Hash.new
11
+ options[:dependencies] ||= Hash.new
12
+ cookbook = RemoteCookbook.new(name, version,
13
+ CacheBuilder::Worker::Opscode.worker_type, SiteConnector::Opscode::V1_API)
14
+ metadata = Ridley::Chef::Cookbook::Metadata.new
15
+ options[:platforms].each { |name, version| metadata.supports(name, version) }
16
+ options[:dependencies].each { |name, constraint| metadata.depends(name, constraint) }
17
+ cache_manager.add(cookbook, metadata)
18
+ end
19
+ end
20
+ end