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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +17 -0
- data/CONTRIBUTING.md +33 -0
- data/Gemfile +40 -0
- data/Guardfile +20 -0
- data/LICENSE +201 -0
- data/README.md +37 -0
- data/Thorfile +39 -0
- data/berkshelf-api.gemspec +35 -0
- data/bin/berks-api +5 -0
- data/lib/berkshelf-api.rb +1 -0
- data/lib/berkshelf/api.rb +25 -0
- data/lib/berkshelf/api/application.rb +114 -0
- data/lib/berkshelf/api/cache_builder.rb +60 -0
- data/lib/berkshelf/api/cache_builder/worker.rb +116 -0
- data/lib/berkshelf/api/cache_builder/worker/chef_server.rb +46 -0
- data/lib/berkshelf/api/cache_builder/worker/opscode.rb +59 -0
- data/lib/berkshelf/api/cache_manager.rb +96 -0
- data/lib/berkshelf/api/config.rb +23 -0
- data/lib/berkshelf/api/cucumber.rb +11 -0
- data/lib/berkshelf/api/dependency_cache.rb +123 -0
- data/lib/berkshelf/api/endpoint.rb +17 -0
- data/lib/berkshelf/api/endpoint/v1.rb +19 -0
- data/lib/berkshelf/api/errors.rb +8 -0
- data/lib/berkshelf/api/generic_server.rb +50 -0
- data/lib/berkshelf/api/logging.rb +37 -0
- data/lib/berkshelf/api/mixin.rb +7 -0
- data/lib/berkshelf/api/mixin/services.rb +48 -0
- data/lib/berkshelf/api/rack_app.rb +5 -0
- data/lib/berkshelf/api/remote_cookbook.rb +3 -0
- data/lib/berkshelf/api/rest_gateway.rb +62 -0
- data/lib/berkshelf/api/rspec.rb +20 -0
- data/lib/berkshelf/api/rspec/server.rb +29 -0
- data/lib/berkshelf/api/site_connector.rb +7 -0
- data/lib/berkshelf/api/site_connector/opscode.rb +162 -0
- data/lib/berkshelf/api/srv_ctl.rb +63 -0
- data/lib/berkshelf/api/version.rb +5 -0
- data/spec/fixtures/reset.pem +27 -0
- data/spec/spec_helper.rb +53 -0
- data/spec/support/actor_mocking.rb +7 -0
- data/spec/support/chef_server.rb +73 -0
- data/spec/unit/berkshelf/api/application_spec.rb +24 -0
- data/spec/unit/berkshelf/api/cache_builder/worker/chef_server_spec.rb +59 -0
- data/spec/unit/berkshelf/api/cache_builder/worker/opscode_spec.rb +41 -0
- data/spec/unit/berkshelf/api/cache_builder/worker_spec.rb +80 -0
- data/spec/unit/berkshelf/api/cache_builder_spec.rb +37 -0
- data/spec/unit/berkshelf/api/cache_manager_spec.rb +123 -0
- data/spec/unit/berkshelf/api/config_spec.rb +24 -0
- data/spec/unit/berkshelf/api/dependency_cache_spec.rb +109 -0
- data/spec/unit/berkshelf/api/endpoint/v1_spec.rb +18 -0
- data/spec/unit/berkshelf/api/logging_spec.rb +28 -0
- data/spec/unit/berkshelf/api/mixin/services_spec.rb +68 -0
- data/spec/unit/berkshelf/api/rack_app_spec.rb +6 -0
- data/spec/unit/berkshelf/api/rest_gateway_spec.rb +26 -0
- data/spec/unit/berkshelf/api/site_connector/opscode_spec.rb +85 -0
- data/spec/unit/berkshelf/api/srv_ctl_spec.rb +56 -0
- 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,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,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,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
|