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