jerakia 1.1.2 → 1.2.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hiera/backend/jerakia_backend.rb +13 -14
  3. data/lib/jerakia/answer.rb +28 -27
  4. data/lib/jerakia/cache/entry.rb +2 -6
  5. data/lib/jerakia/cache/file.rb +53 -23
  6. data/lib/jerakia/cache.rb +44 -11
  7. data/lib/jerakia/cli/lookup.rb +124 -0
  8. data/lib/jerakia/cli/server.rb +50 -0
  9. data/lib/jerakia/cli/token.rb +64 -0
  10. data/lib/jerakia/cli.rb +7 -117
  11. data/lib/jerakia/config.rb +5 -5
  12. data/lib/jerakia/datasource/dummy.rb +1 -6
  13. data/lib/jerakia/datasource/file/json.rb +1 -3
  14. data/lib/jerakia/datasource/file/yaml.rb +1 -3
  15. data/lib/jerakia/datasource/file.rb +21 -44
  16. data/lib/jerakia/datasource/http.rb +17 -23
  17. data/lib/jerakia/datasource.rb +37 -36
  18. data/lib/jerakia/dsl/lookup.rb +4 -6
  19. data/lib/jerakia/dsl/policy.rb +11 -12
  20. data/lib/jerakia/error.rb +0 -5
  21. data/lib/jerakia/launcher.rb +26 -30
  22. data/lib/jerakia/log.rb +21 -22
  23. data/lib/jerakia/lookup/plugin/hiera.rb +3 -4
  24. data/lib/jerakia/lookup/plugin.rb +5 -6
  25. data/lib/jerakia/lookup/plugin_config.rb +31 -0
  26. data/lib/jerakia/lookup/pluginfactory.rb +30 -36
  27. data/lib/jerakia/lookup.rb +31 -32
  28. data/lib/jerakia/policy.rb +60 -45
  29. data/lib/jerakia/request.rb +3 -2
  30. data/lib/jerakia/response/filter/encryption.rb +7 -12
  31. data/lib/jerakia/response/filter/strsub.rb +4 -9
  32. data/lib/jerakia/response/filter.rb +5 -5
  33. data/lib/jerakia/response.rb +7 -13
  34. data/lib/jerakia/schema.rb +23 -35
  35. data/lib/jerakia/scope/metadata.rb +0 -1
  36. data/lib/jerakia/scope/puppetdb.rb +38 -0
  37. data/lib/jerakia/scope/server.rb +60 -0
  38. data/lib/jerakia/scope/yaml.rb +3 -4
  39. data/lib/jerakia/scope.rb +0 -2
  40. data/lib/jerakia/server/auth/token.rb +35 -0
  41. data/lib/jerakia/server/auth.rb +72 -0
  42. data/lib/jerakia/server/rest.rb +140 -0
  43. data/lib/jerakia/server.rb +41 -0
  44. data/lib/jerakia/util.rb +6 -7
  45. data/lib/jerakia/version.rb +1 -3
  46. data/lib/jerakia.rb +58 -40
  47. data/lib/puppet/indirector/data_binding/jerakia.rb +9 -11
  48. data/lib/puppet/indirector/data_binding/jerakia_rest.rb +11 -13
  49. metadata +78 -11
@@ -1,19 +1,17 @@
1
1
  class Jerakia::Schema
2
-
3
2
  # Arguments: request(Jerakia::Request), opts(Hash)
4
3
  #
5
- def initialize(request,opts)
6
- schema_datasource=datasource(opts)
7
- schema_request=Jerakia::Request.new(
4
+ def initialize(request, opts)
5
+ schema_datasource = datasource(opts)
6
+ schema_request = Jerakia::Request.new(
8
7
  :metadata => request.metadata,
9
8
  :key => request.key,
10
9
  :namespace => request.namespace,
11
- :use_schema => false,
10
+ :use_schema => false
12
11
  )
13
12
 
14
13
  Jerakia.log.debug("Schema lookup invoked for #{request.key} namespace: #{request.namespace}")
15
- schema_lookup = Jerakia::Launcher.new(schema_request)
16
-
14
+ schema_lookup = Jerakia::Launcher.new(schema_request)
17
15
 
18
16
  begin
19
17
  schema_lookup.evaluate do
@@ -27,58 +25,48 @@ class Jerakia::Schema
27
25
  raise Jerakia::SchemaError, "Schema lookup for #{request.key} failed: #{e.message}"
28
26
  end
29
27
 
30
-
31
28
  @schema_data = schema_lookup.answer.payload || {}
32
29
 
33
30
  # Validate the returned data from the schema
34
31
  raise Jerakia::SchemaError, "Schema must return a hash for key #{request.key}" unless @schema_data.is_a?(Hash)
35
32
 
36
- valid_opts = [ "alias", "cascade", "merge" ]
33
+ valid_opts = %w(alias cascade merge)
37
34
  @schema_data.keys.each do |key|
38
35
  unless valid_opts.include?(key)
39
36
  raise Jerakia::SchemaError, "Unknown schema option #{key} for key #{request.key}"
40
37
  end
41
38
  end
42
39
 
43
-
44
-
45
-
46
40
  Jerakia.log.debug("Schema returned #{@schema_data}")
47
41
 
48
- if salias = @schema_data["alias"]
49
- Jerakia.log.debug("Schema alias found to #{@schema_data["alias"]}")
50
- request.namespace=Array(salias["namespace"]) if salias["namespace"]
51
- request.key = salias["key"] if salias["key"]
42
+ if salias = @schema_data['alias']
43
+ Jerakia.log.debug("Schema alias found to #{@schema_data['alias']}")
44
+ request.namespace = Array(salias['namespace']) if salias['namespace']
45
+ request.key = salias['key'] if salias['key']
52
46
  end
53
47
 
54
-
55
- if @schema_data["cascade"]
48
+ if @schema_data['cascade']
56
49
  Jerakia.log.debug("Overriding lookup_type from #{request.lookup_type} to :cascade")
57
- request.lookup_type= :cascade
50
+ request.lookup_type = :cascade
58
51
  end
59
52
 
60
- if @schema_data["merge"]
61
- if ["array", "hash", "deep_hash"].include?(@schema_data["merge"])
62
- request.merge = @schema_data["merge"].to_sym
53
+ if @schema_data['merge']
54
+ if %w(array hash deep_hash).include?(@schema_data['merge'])
55
+ request.merge = @schema_data['merge'].to_sym
63
56
  else
64
- raise Jerakia::SchemaError, "Invalid merge type #{@schema_data['merge']} found in schema for key #{request.key}"
57
+ raise Jerakia::SchemaError, "Invalid merge type #{@schema_data['merge']} found in schema for key #{request.key}"
65
58
  end
66
59
  end
67
-
68
60
  end
69
61
 
70
- def datasource(opts={})
71
- [
62
+ def datasource(opts = {})
63
+ [
72
64
  :file, {
73
- :docroot => opts["docroot"] || "/var/lib/jerakia/schema",
74
- :format => opts["format"] || :json,
75
- :enable_caching => opts["enable_caching"] || true,
76
- :searchpath => [ '' ],
65
+ :docroot => opts['docroot'] || '/var/lib/jerakia/schema',
66
+ :format => opts['format'] || :json,
67
+ :enable_caching => opts['enable_caching'] || true,
68
+ :searchpath => ['']
77
69
  }
78
- ]
70
+ ]
79
71
  end
80
-
81
-
82
72
  end
83
-
84
-
@@ -15,4 +15,3 @@ class Jerakia::Scope
15
15
  end
16
16
  end
17
17
  end
18
-
@@ -0,0 +1,38 @@
1
+ require 'lookup_http'
2
+ class Jerakia::Scope
3
+ module Puppetdb
4
+ def create
5
+ yaml_file = request.scope_options['file'] || './jerakia_scope.yaml'
6
+ puppetdb_host = request.scope_options['puppetdb_host'] || 'localhost'
7
+ puppetdb_port = request.scope_options['puppetdb_port'] || 8080
8
+ puppetdb_api = request.scope_options['puppetdb_api'] || 4
9
+ node = request.scope_options['node']
10
+
11
+ raise Jerakia::Error, "Must pass the option node to the puppetdb scope handler" unless node
12
+
13
+ connection_opts = {
14
+ :host => puppetdb_host,
15
+ :port => puppetdb_port,
16
+ :output => 'json',
17
+ :ignore_404 => true
18
+ }.merge(request.scope_options['puppetdb_http_opts'] || {})
19
+
20
+ puppetdb_con = LookupHttp.new(connection_opts)
21
+
22
+ case puppetdb_api
23
+ when 4
24
+ path = "/pdb/query/v4/nodes/#{node}/facts"
25
+ else
26
+ raise Jerakia::Error, "Unsupported PuppetDB API version, #{puppetdb_api}"
27
+ end
28
+
29
+ Jerakia.log.debug("Sending HTTP query to PuppetDB #{puppetdb_host}:#{puppetdb_port} at path #{path}")
30
+
31
+ response = puppetdb_con.get_parsed(path)
32
+
33
+ raise Jerakia::Error, "PuppetDB returned no data for node #{node}" unless response.is_a?(Array)
34
+
35
+ response.each { |r| value[r['name'].to_sym] = r['value'] }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,60 @@
1
+ require 'securerandom'
2
+ require 'data_mapper'
3
+ # The server scope handler can store and retrieve scope data server side
4
+ #
5
+ class Jerakia::Scope
6
+ module Server
7
+ class Database
8
+ DataMapper.setup(:scope, "sqlite://#{Jerakia.config[:databasedir]}/scope.db")
9
+
10
+ class Resource
11
+ include DataMapper::Resource
12
+
13
+
14
+ def self.default_repository_name
15
+ :scope
16
+ end
17
+
18
+ property :id, Serial, :key => true
19
+ property :identifier, String, :index => true
20
+ property :realm, String, :index => true
21
+ property :uuid, String
22
+ property :scope, Object
23
+ end
24
+
25
+ DataMapper.repository(:scope).auto_upgrade!
26
+ DataMapper.repository(:scope).auto_migrate!
27
+ end
28
+
29
+ def create
30
+ realm = request.scope_options['realm']
31
+ identifier = request.scope_options['identifier']
32
+
33
+ raise Jerakia::Error, "Must supply realm and identifier for server scope handler" unless realm and identifier
34
+ resource = Jerakia::Scope::Server.find(realm, identifier)
35
+ raise Jerakia::Error, "No scope data found for realm:#{realm} identifier:#{identifier}" if resource.nil?
36
+ scope = resource.scope
37
+ raise Jerakia::Error, "Scope did not return a hash for realm:#{realm} identifier:#{identifier}" unless scope.is_a?(Hash)
38
+ @value = Hash[ scope.map { |k,v| [ k.to_sym, v ] } ]
39
+ end
40
+
41
+ class << self
42
+ def find(realm, identifier)
43
+ Database::Resource.first(:identifier => identifier, :realm => realm)
44
+ end
45
+
46
+
47
+ def store(realm, identifier, scope)
48
+ uuid = SecureRandom.uuid
49
+ entry = find(realm, identifier)
50
+ if entry.nil?
51
+ Database::Resource.create(:identifier => identifier, :realm => realm, :scope => scope, :uuid => uuid)
52
+ else
53
+ entry.update({:scope => scope, :uuid => uuid})
54
+ entry.save
55
+ end
56
+ uuid
57
+ end
58
+ end
59
+ end
60
+ end
@@ -2,13 +2,12 @@
2
2
  class Jerakia::Scope
3
3
  module Yaml
4
4
  def create
5
- yaml_file = request.scope_options["file"] || "./jerakia_scope.yaml"
6
- raise Jerakia::Error, "Scope file #{yaml_file} not found" unless File.exists?(yaml_file)
5
+ yaml_file = request.scope_options['file'] || './jerakia_scope.yaml'
6
+ raise Jerakia::Error, "Scope file #{yaml_file} not found" unless File.exist?(yaml_file)
7
7
  data = YAML.load(File.read(yaml_file))
8
- data.each do |key,val|
8
+ data.each do |key, val|
9
9
  value[key.to_sym] = val
10
10
  end
11
11
  end
12
12
  end
13
13
  end
14
-
data/lib/jerakia/scope.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  class Jerakia::Scope
2
-
3
2
  attr_reader :value
4
3
  attr_reader :handler
5
4
  attr_reader :request
@@ -12,5 +11,4 @@ class Jerakia::Scope
12
11
  instance_eval "extend Jerakia::Scope::#{@handler.to_s.capitalize}"
13
12
  create
14
13
  end
15
-
16
14
  end
@@ -0,0 +1,35 @@
1
+ require 'data_mapper'
2
+ require 'dm-sqlite-adapter'
3
+ require 'bcrypt'
4
+ require 'jerakia'
5
+ require 'jerakia/config'
6
+ class Jerakia
7
+ class Server
8
+ class Auth
9
+
10
+ Jerakia.log.debug("Authentication database sqlite://#{Jerakia.config[:vardir]}/tokens.db")
11
+
12
+ DataMapper.setup(:tokens, "sqlite://#{Jerakia.config[:databasedir]}/tokens.db")
13
+
14
+ class Token
15
+
16
+ include DataMapper::Resource
17
+ include BCrypt
18
+
19
+ def self.default_repository_name
20
+ :tokens
21
+ end
22
+
23
+ property :api_id, String, :key => true
24
+ property :token, BCryptHash
25
+ property :active, Boolean, :default => true
26
+ property :last_seen, DateTime, :default => DateTime.now
27
+ end
28
+
29
+ DataMapper.finalize
30
+ DataMapper.auto_upgrade!
31
+ end
32
+ end
33
+ end
34
+
35
+
@@ -0,0 +1,72 @@
1
+ require 'jerakia/server/auth/token'
2
+ require 'securerandom'
3
+
4
+ class Jerakia
5
+ class Server
6
+ class Auth
7
+
8
+ class << self
9
+
10
+ def generate_token
11
+ SecureRandom.hex(40)
12
+ end
13
+
14
+
15
+ def get_entry(api_id)
16
+ Jerakia::Server::Auth::Token.get(api_id)
17
+ end
18
+
19
+ def update(api_id, fields)
20
+ entry = get_entry(api_id)
21
+ entry.update(fields)
22
+ entry.save
23
+ end
24
+
25
+ def seen!(api_id)
26
+ update(api_id, { :last_seen => DateTime.now })
27
+ end
28
+
29
+ def disable(api_id)
30
+ update(api_id, { :active => false })
31
+ end
32
+
33
+ def enable(api_id)
34
+ update(api_id, { :active => true })
35
+ end
36
+
37
+ def destroy(api_id)
38
+ entry = get_entry(api_id)
39
+ entry.destroy
40
+ end
41
+
42
+ def exists?(api_id)
43
+ get_entry(api_id)
44
+ end
45
+
46
+ def create(api_id)
47
+ raise Jerakia::Error, "API ID #{api_id} already exists" if exists?(api_id)
48
+ token = generate_token
49
+ entry = Jerakia::Server::Auth::Token.new(:api_id => api_id, :token => token)
50
+ entry.save
51
+ api_id + ":" + token
52
+ end
53
+
54
+ def get_tokens
55
+ Jerakia::Server::Auth::Token.find
56
+ end
57
+
58
+ def authenticate(token_string)
59
+ api_id, token = token_string.split(/:/)
60
+ entry = get_entry(api_id)
61
+ return false if entry.nil?
62
+ if entry.token == token and entry.active
63
+ seen!(api_id)
64
+ true
65
+ else
66
+ false
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,140 @@
1
+ require 'sinatra'
2
+ require 'jerakia'
3
+ require 'jerakia/server/auth'
4
+ require 'json'
5
+ require 'jerakia/scope/server'
6
+
7
+ class Jerakia
8
+ class Server
9
+ class Rest < Sinatra::Base
10
+
11
+ def self.jerakia
12
+ Jerakia::Server.jerakia
13
+ end
14
+
15
+ def initialize
16
+ @authorized_tokens={}
17
+ super
18
+ end
19
+
20
+ def jerakia
21
+ self.class.jerakia
22
+ end
23
+
24
+ def auth_denied
25
+ request_failed('unauthorized', 401)
26
+ end
27
+
28
+ def token_ttl
29
+ Jerakia::Server.config["token_ttl"]
30
+ end
31
+
32
+ def token_valid?(token)
33
+ return false unless @authorized_tokens[token].is_a?(Time)
34
+ (Time.now - @authorized_tokens[token]) < token_ttl.to_i
35
+ end
36
+
37
+ def authenticate!
38
+ token = env['HTTP_X_AUTHENTICATION']
39
+ auth_denied if token.nil?
40
+ return true if token_valid?(token)
41
+ unless Jerakia::Server::Auth.authenticate(token)
42
+ auth_denied
43
+ end
44
+ @authorized_tokens[token] = Time.now
45
+ end
46
+
47
+ before do
48
+ content_type 'application/json'
49
+ end
50
+
51
+ get '/' do
52
+ auth_denied
53
+ end
54
+
55
+ def request_failed(message, status_code=501)
56
+ halt(status_code, {
57
+ :status => 'failed',
58
+ :message => message,
59
+ }.to_json)
60
+ end
61
+
62
+ def mandatory_params(mandatory, params)
63
+ mandatory.each do |m|
64
+ unless params.include?(m)
65
+ request_failed("Must include parameter #{m} in request", 400)
66
+ end
67
+ end
68
+ end
69
+
70
+ get '/v1/lookup' do
71
+ request_failed("Keyless lookups not supported in this version of Jerakia")
72
+ end
73
+
74
+ get '/v1/lookup/:key' do
75
+ mandatory_params(['namespace'], params)
76
+ request_opts = {
77
+ :key => params['key'],
78
+ :namespace => params['namespace'].split(/\//),
79
+ }
80
+
81
+ metadata = params.select { |k,v| k =~ /^metadata_.*/ }
82
+ scope_opts = params.select { |k,v| k =~ /^scope_.*/ }
83
+
84
+ request_opts[:metadata] = Hash[metadata.map { |k,v| [k.gsub(/^metadata_/, ""), v] }]
85
+ request_opts[:scope_options] = Hash[scope_opts.map { |k,v| [k.gsub(/^scope_/, ""), v] }]
86
+
87
+
88
+ request_opts[:policy] = params['policy'].to_sym if params['policy']
89
+ request_opts[:lookup_type] = params['lookup_type'].to_sym if params['lookup_type']
90
+ request_opts[:merge] = params['merge'].to_sym if params['merge']
91
+ request_opts[:scope] = params['scope'].to_sym if params['scope']
92
+ request_opts[:use_schema] = false if params['use_schema'] == 'false'
93
+
94
+ begin
95
+ request = Jerakia::Request.new(request_opts)
96
+ answer = jerakia.lookup(request)
97
+ rescue Jerakia::Error => e
98
+ request_failed(e.message, 501)
99
+ end
100
+ {
101
+ :status => 'ok',
102
+ :payload => answer.payload
103
+ }.to_json
104
+ end
105
+
106
+ get '/v1/scope/:realm/:identifier' do
107
+ resource = Jerakia::Scope::Server.find(params['realm'], params['identifier'])
108
+ if resource.nil?
109
+ halt(404, { :status => 'failed', :message => "No scope data found" }.to_json)
110
+ else
111
+ {
112
+ :status => 'ok',
113
+ :payload => resource.scope
114
+ }.to_json
115
+ end
116
+ end
117
+
118
+ put '/v1/scope/:realm/:identifier' do
119
+ scope = JSON.parse(request.body.read)
120
+ uuid = Jerakia::Scope::Server.store(params['realm'], params['identifier'], scope)
121
+ {
122
+ :status => 'ok',
123
+ :uuid => uuid
124
+ }.to_json
125
+ end
126
+
127
+ get '/v1/scope/:realm/:identifier/uuid' do
128
+ resource = Jerakia::Scope::Server.find(params['realm'], params['identifier'])
129
+ if resource.nil?
130
+ request_failed('No scope data found', 404)
131
+ else
132
+ {
133
+ :status => 'ok',
134
+ :uuid => resource.uuid
135
+ }.to_json
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,41 @@
1
+ require 'sinatra'
2
+ require 'jerakia'
3
+ require 'thin'
4
+ class Jerakia
5
+ class Server
6
+
7
+ def jerakia
8
+ self.class.jerakia
9
+ end
10
+
11
+ class << self
12
+
13
+ @jerakia = nil
14
+ @config = {}
15
+
16
+ attr_reader :config
17
+
18
+ def default_config
19
+ {
20
+ 'bind' => '127.0.0.1',
21
+ 'port' => '9843',
22
+ 'token_ttl' => 300,
23
+ }
24
+ end
25
+
26
+ def jerakia
27
+ @jerakia
28
+ end
29
+
30
+ def start(opts)
31
+ @jerakia = Jerakia.new(opts)
32
+ require 'jerakia/server/rest'
33
+ @config = default_config.merge(Jerakia.config[:server] || {})
34
+ Thin::Logging.logger=Jerakia.log.logger
35
+ Jerakia::Server::Rest.set :bind, @config['bind']
36
+ Jerakia::Server::Rest.set :port, @config['port']
37
+ Jerakia::Server::Rest.run!
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/jerakia/util.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  class Jerakia
2
2
  module Util
3
3
  class << self
4
- def autoload(path,mod)
4
+ def autoload(path, mod)
5
5
  Jerakia.log.debug "autoloading #{path} #{mod}"
6
- require "jerakia/#{path}/#{mod.to_s}"
6
+ require "jerakia/#{path}/#{mod}"
7
7
  end
8
8
 
9
9
  def walk(data)
@@ -21,7 +21,7 @@ class Jerakia
21
21
  end
22
22
 
23
23
  def walk_hash(data)
24
- data.inject({}) do |h,(k,v)|
24
+ data.each_with_object({}) do |(_k, v), h|
25
25
  if v.is_a?(Hash)
26
26
  walk_hash(v) { |x| yield x }
27
27
  elsif v.is_a?(Array)
@@ -29,9 +29,9 @@ class Jerakia
29
29
  else
30
30
  yield v
31
31
  end
32
- h
32
+ h
33
33
  end
34
- return data
34
+ data
35
35
  end
36
36
 
37
37
  def walk_array(data)
@@ -44,9 +44,8 @@ class Jerakia
44
44
  yield element
45
45
  end
46
46
  element
47
- end
47
+ end
48
48
  end
49
49
  end
50
50
  end
51
51
  end
52
-
@@ -1,9 +1,7 @@
1
1
  class Jerakia
2
-
3
2
  # Public API to retrieve the latest released version of the code
4
3
  #
5
4
  # This should be updated when a new gem is released and it is read from the gemspec file
6
5
  #
7
- VERSION = "1.1.2"
8
-
6
+ VERSION = '1.2.0'.freeze
9
7
  end