sis_ruby 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 584c4065727e96b0b4c0172fe31b6ab7d8a1d39a
4
+ data.tar.gz: 99dd517363e6e832f3e2c910f2b7a36ecb925a9e
5
+ SHA512:
6
+ metadata.gz: 001f9546fc5f481bb3b4677071f9d4dd924975810ab34ee25c83f3e749fb7ecfe367d5d74104bf04f76852fa05fb605f37c56c1e371ce9d3b7f063b623b75f91
7
+ data.tar.gz: 08a35bf1b678a39e0cb5b0826ee5c2c391c97fdec62df27e7c5833a61830ce3924fcb76f47228ff3757f9d0d7d1c71ab796104301e2885f6c041e4ff5d997e6f
data/.gitignore ADDED
@@ -0,0 +1,41 @@
1
+ ### Ruby template
2
+ *.gem
3
+ *.rbc
4
+ /.config
5
+ /coverage/
6
+ /InstalledFiles
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /test/tmp/
11
+ /test/version_tmp/
12
+ /tmp/
13
+
14
+ ## Specific to RubyMotion:
15
+ .dat*
16
+ .repl_history
17
+ build/
18
+
19
+ ## Documentation cache and generated files:
20
+ /.yardoc/
21
+ /_yardoc/
22
+ /doc/
23
+ /rdoc/
24
+
25
+ ## Environment normalisation:
26
+ /.bundle/
27
+ /vendor/bundle
28
+ /lib/bundler/man/
29
+
30
+ # for a library or gem, you might want to ignore these files since the code is
31
+ # intended to run in multiple environments; otherwise, check them in:
32
+ # Gemfile.lock
33
+ # .ruby-version
34
+ # .ruby-gemset
35
+
36
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
37
+ .rvmrc
38
+
39
+ # Created by .ignore support plugin (hsz.mobi)
40
+
41
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ This software is licensed under the BSD 3-Clause license.
2
+
3
+ Copyright (c) 2016, Verisign, Inc. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation and/or
13
+ other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its contributors
16
+ may be used to endorse or promote products derived from this software without
17
+ specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,188 @@
1
+ # sis_ruby
2
+
3
+ A Ruby client to talk to a SIS server (https://github.com/sis-cmdb/sis-api).
4
+
5
+ Currently only the v1.1 API is supported.
6
+
7
+ Initial commit represents work by Neel Goyal, formerly at Verisign,
8
+ and Keith Bennett, currently at Verisign (http://www.verisign.com/).
9
+
10
+
11
+ ## Usage
12
+
13
+ Example code:
14
+
15
+ ```
16
+ require 'sis_ruby'
17
+
18
+ # Create a client
19
+ client = SisRuby::Client.new(sis_server_url)
20
+
21
+ # Get the host information endpoint
22
+ hosts = client.entities('host')
23
+
24
+ # Use all possible Params options to list records of a given entity:
25
+ params = SisRuby::Params.new.sort('hostname').offset(9000).limit(2).fields('hostname', 'environment')
26
+ records = hosts.list(params)
27
+
28
+ # Get record counts:
29
+ puts "Total host count is #{hosts.count}"
30
+ puts "Total qa host count is #{hosts.count('environment' => 'qa')}"
31
+
32
+ # Get up to 10 hooks
33
+ hook_list = client.hooks.list(SisRuby::Params.new.limit(10))
34
+
35
+ # Get 1 schema
36
+ schema_list = client.schemas.list(SisRuby::Params.new.limit(1)).first
37
+
38
+ # Hiera Entry Creation
39
+ hiera_entry = client.hiera.create({ 'name' => 'entry', 'hieradata' => { 'key1' => 'value1' }});
40
+
41
+ # Entity Deletion: Delete the entity of type 'entity_name' with id 'foo':
42
+ was_deleted = client.entities('entity_name').delete('foo');
43
+ ```
44
+
45
+ ## Client Initialization
46
+
47
+ The client constructor takes the following parameters:
48
+
49
+ * (required) a URL indicating the base URL of the SIS endpoints
50
+ * (optional) a hash containing neither, one, or both of:
51
+ * **:auth_token** - an authorization token field to be sent in the `x-auth-token` header
52
+ * **:api_version** - a version string to use in the request URL's instead of the default API version
53
+
54
+ ```
55
+ client = SisRuby::Client.new(sis_server_url)
56
+ client = SisRuby::Client.new(sis_server_url, auth_token: auth_token)
57
+ ```
58
+
59
+ ## Client Authentication
60
+
61
+ The client may also acquire and use a temporary token to use against the SIS endpoint
62
+ via the `authenticate` method. Below are examples:
63
+
64
+ ```
65
+ # This method will raise an SisRuby::Client::AuthenticationError if authentication fails.
66
+ token = client.authenticate(userid, password)
67
+
68
+ # You can also combine the client creation and authentication into a single expression
69
+ # since the authenticate method returns the client:
70
+ client = SisRuby::Client.new(sis_server_url).authenticate(userid, password)
71
+ ```
72
+
73
+ Although `authenticate` returns the token, there is no need to save it;
74
+ it is stored in the client instance for subsequent requests.
75
+
76
+ ## Entity, Hook, Schema, and Hiera Methods
77
+
78
+ The object returned by `client.hooks`, `client.schemas`, `client.hiera`, and
79
+ `client.entities(entity_name)` all interact with the appropriate endpoints
80
+ and expose the following interface:
81
+
82
+
83
+ ### list(query)
84
+
85
+ This maps to a GET `/` request against the appropriate endpoint for the default or specified API version.
86
+
87
+ List parameters can be specified in the form of any object responding to
88
+ `to_hash` and returning a hash (including, of course, a `Hash`).
89
+
90
+ Values in the hash can contain:
91
+
92
+ * **sort** - field name(s) on which to sort the records, precede fieldname with `-` for descending
93
+ * **limit** - limit the result set size to the specified number of records
94
+ * **fields** - only return the fields passed to this method; however:
95
+ * currently there is a bug that results in array type fields being returned even if they
96
+ are not included in the field list
97
+ * the _id field will always be returned even if it is not specified
98
+ * **offset** - the offset into the sorted results at which to start adding records to the result set
99
+ (Note: offset is not reliable unless a sort order is specified; there is no default sort order
100
+ and the random order may change from call to call.
101
+
102
+ For your convenience, a `Params` class has been provided with chainable methods (see code example).
103
+
104
+ Also, there is a `count` method that will return the total record count. If passed a filter, it will
105
+ return the count of records that meet the filter criteria.
106
+
107
+ An array of hashes is returned on success. For your convenience, there is also a
108
+ `list_as_openstructs` method that returns each record as an `OpenStruct` instance for easier access.
109
+ OpenStruct instances allow you to call methods whose names correspond to the original hash's keys,
110
+ e.g. `host.site` instead of `host['site']`.
111
+ This approach is not recommended for large numbers of records, as it requires more memory and processing.
112
+
113
+
114
+ ### get(id)
115
+
116
+ This maps to a GET `/id` request against the approprivate endpoint for the default or specified API version.
117
+
118
+ * id : a string representing the ID of the object to receive. For schemas, hooks, and hiera, this is the `name`.
119
+ For entities, it is the `_id`.
120
+
121
+ A single hash representing the object is returned on success.
122
+
123
+
124
+ ### create(obj)
125
+
126
+ This maps to a POST `/` request against the appropriate endpoint for the default or specified API version.
127
+
128
+ * obj : a valid Hash conforming to the endpoint specification
129
+
130
+ The created object as a hash is returned on success.
131
+
132
+
133
+ ### update(obj)
134
+
135
+ This maps to a PUT '/' request against the appropriate endpoint for the default or specified API version.
136
+
137
+ * obj : a valid hash conforming to the endpoint specification. Typically retrieved from `list` or `get`.
138
+
139
+ The updated hash representing the object is returned on success.
140
+
141
+
142
+ ### delete(obj)
143
+
144
+ This maps to a DELETE '/id' request against the appropriate endpoint for the default or specified API version.
145
+
146
+ * obj : either a string id or an object retrieved from `list` or `get`
147
+
148
+ The boolean `true` is returned on success.
149
+
150
+
151
+ ## Error handling
152
+
153
+ An instance of `SisRuby::Client` will raise an exception if an HTTP request returns a status code above and including 400.
154
+
155
+
156
+ ## Running the Tests
157
+
158
+ Test code is divided into different top-level directories:
159
+
160
+ ### `spec`
161
+
162
+ These are tests not requiring a SIS server.
163
+
164
+ ### `spec_reading`
165
+
166
+ These are tests requiring a SIS server containing a 'host' entity populated with records
167
+
168
+ ### `spec_writing`
169
+
170
+ These are tests requiring a SIS server that can be used for writing; for these
171
+ you'll probably want to set up a local server. Instructions for this are at
172
+ https://github.com/sis-cmdb/sis-api#building-and-testing.
173
+
174
+ If the tests should ever fail and the data base is not correctly restored to
175
+ its original empty state, you can delete the Mongo data
176
+ by running the Mongo shell ('mongo') and issuing the following commands:
177
+
178
+ ```
179
+ use test
180
+ db.dropDatabase();
181
+ ```
182
+
183
+ To verify the user id and password, you can issue the following command, replacing 'test' and
184
+ 'abc123' with the real user id and password:
185
+
186
+ ```
187
+ curl -D- -u test:abc123 -X POST -H "Content-Type: application/json" http://localhost:3000/api/v1.1/users/auth_token
188
+ ```
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v1.0.0
2
+
3
+ Code base copied from @ngoyal's original version internal to Verisign, with extensive modifications.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ # From https://github.com/goncalossilva/gem_template
2
+ begin
3
+ require "bundler"
4
+ Bundler.setup
5
+ rescue LoadError
6
+ $stderr.puts "You need to have Bundler installed to be able build this gem."
7
+ end
8
+
9
+ gemspec = eval(File.read('sis_ruby.gemspec'))
10
+
11
+ desc "Validate the gemspec"
12
+ task :gemspec do
13
+ gemspec.validate
14
+ end
15
+
16
+ desc "Build gem locally"
17
+ task :build => :gemspec do
18
+ system "gem build #{gemspec.name}.gemspec"
19
+ FileUtils.mkdir_p "pkg"
20
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", "pkg"
21
+ end
22
+
23
+ desc "Install gem locally"
24
+ task :install => :build do
25
+ system "gem install pkg/#{gemspec.name}-#{gemspec.version}.gem"
26
+ end
27
+
28
+ desc "Clean automatically generated files"
29
+ task :clean do
30
+ FileUtils.rm_rf "pkg"
31
+ end
32
+
33
+ require 'rspec/core/rake_task'
34
+
35
+ RSpec::Core::RakeTask.new('spec')
@@ -0,0 +1,85 @@
1
+ require_relative 'common'
2
+ require 'sis_ruby/endpoint'
3
+ require 'typhoeus'
4
+
5
+ module SisRuby
6
+
7
+ class Client
8
+
9
+ attr_reader :api_version, :auth_token, :base_url, :entity_endpoints, :hiera, :hooks, :hosts, :schemas
10
+
11
+ DEFAULT_API_VERSION = '1.1'
12
+
13
+ # @param url the base URL of the service (excluding ''/api/v...'')
14
+ # @param options (can include :version, :auth_token)
15
+ def initialize(url, options = {})
16
+ @base_url = url
17
+ @api_version = options[:api_version] || DEFAULT_API_VERSION
18
+ @auth_token = options[:auth_token]
19
+
20
+ @entity_endpoints = {}
21
+ @hooks = create_endpoint('hooks', 'name')
22
+ @schemas = create_endpoint('schemas', 'name')
23
+ @hiera = create_endpoint('hiera', 'name')
24
+ end
25
+
26
+
27
+ def create_endpoint(endpoint_suffix, id_fieldname = :default)
28
+ Endpoint.new(self, endpoint_suffix, id_fieldname)
29
+ end
30
+
31
+
32
+ def entities(name, id_fieldname = DEFAULT_ID_FIELDNAME)
33
+ @entity_endpoints[name] ||= create_endpoint("entities/#{name}", id_fieldname)
34
+ end
35
+
36
+
37
+ def tokens(username)
38
+ create_endpoint("users/#{username}/tokens", 'name')
39
+ end
40
+
41
+
42
+ # Returns the schema for the specified collection_name, or nil if it's not found.
43
+ def schema_for(collection_name)
44
+ params = Params.new.limit(1).filter('name' => collection_name)
45
+ schemas.list(params).first
46
+ end
47
+
48
+
49
+ # Authenticates the username and password. Get the token by calling client.auth_token.
50
+ # @return self for chaining this method after the constructor
51
+ def authenticate(username, password)
52
+ dest = "#{base_url}/api/v#{api_version}/users/auth_token"
53
+ options = {
54
+ userpwd: username + ':' + password,
55
+ headers: { 'Accept' => 'application/json', 'Content-Type' => 'application/json' }
56
+ }
57
+ response = Typhoeus.post(dest, options)
58
+ unless response.options[:response_code] == 201
59
+ raise AuthenticationError.new(response)
60
+ end
61
+
62
+ @auth_token = JSON.parse(response.response_body)['name']
63
+ self
64
+ end
65
+
66
+
67
+ def to_s
68
+ self.class.name + ": base_url = #{@base_url}"
69
+ end
70
+
71
+
72
+
73
+ class AuthenticationError < Exception
74
+
75
+ def initialize(response)
76
+ @response = response
77
+ end
78
+
79
+ def to_s
80
+ @response.options[:response_headers].split("\r\n").first
81
+ end
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ module SisRuby
2
+
3
+ DEFAULT_ID_FIELDNAME = '_id'
4
+
5
+ end
@@ -0,0 +1,108 @@
1
+ require 'typhoeus'
2
+ require 'json'
3
+
4
+ require_relative 'client'
5
+ require_relative 'common'
6
+ require_relative 'get_helper'
7
+ require_relative 'result_enumerable'
8
+ require_relative 'exceptions/missing_id_error'
9
+
10
+ module SisRuby
11
+
12
+ class Endpoint
13
+
14
+ include GetHelper
15
+
16
+ attr_reader :client, :id_field, :url
17
+
18
+
19
+ def initialize(client, endpoint_name, id_field = DEFAULT_ID_FIELDNAME)
20
+ @client = client
21
+ @url = "#{client.base_url}/api/v#{client.api_version}/#{endpoint_name}"
22
+ @id_field = id_field
23
+ end
24
+
25
+
26
+ # This method is used to allow callers to pass either the id itself,
27
+ # or the record including an id key/value pair.
28
+ def id_from_param(object)
29
+ id = object.is_a?(Hash) ? object[@id_field] : object
30
+ id ? id : raise(MissingIdError.new("Missing required id field #{@id_field}}"))
31
+ end
32
+
33
+
34
+ def create_enumerable(params = {}, chunk_size = ResultEnumerable::DEFAULT_CHUNK_RECORD_COUNT)
35
+ ResultEnumerable.new(self, params, chunk_size)
36
+ end
37
+
38
+
39
+ # Gets the total count of records, with optional filter
40
+ def count(filter = {})
41
+ params = Params.new.filter(filter).limit(1).to_hash
42
+ response = Typhoeus::Request.new(@url, params: params, headers: create_headers(true)).run
43
+ response.headers['x-total-count'].to_i
44
+ end
45
+
46
+
47
+ # Anything implementing a to_hash method can be passed as the query.
48
+ # This enables the passing in of SisParams objects.
49
+ def list(params = {})
50
+ create_enumerable(params).each.to_a
51
+ end
52
+
53
+
54
+ # Anything implementing a to_hash method can be passed as the query.
55
+ # This enables the passing in of SisParams objects.
56
+ def list_as_openstructs(query = {})
57
+ list(query).map { |h| OpenStruct.new(h) }
58
+ end
59
+
60
+
61
+ def get(id)
62
+ request = Typhoeus::Request.new("#{url}/#{id}", headers: create_headers(true))
63
+ response = request.run
64
+ validate_response_success(response)
65
+ JSON.parse(response.body)
66
+ end
67
+
68
+
69
+ def create(obj)
70
+ http_response = Typhoeus.post(@url, { body: obj.to_json, headers: get_headers(true) } )
71
+ validate_response_success(http_response)
72
+ JSON.parse(http_response.body)
73
+ end
74
+
75
+
76
+ def delete(id)
77
+ id = id_from_param(id)
78
+ http_response = Typhoeus.delete("#{@url}/#{id}", headers: get_headers(false))
79
+ validate_response_success(http_response)
80
+ JSON.parse(http_response.body)
81
+ end
82
+
83
+
84
+ def update(obj)
85
+ id = id_from_param(obj)
86
+ http_response = self.class.put("#{@url}/#{id}", {body: obj.to_json, headers: get_headers(true) })
87
+ validate_response_success(http_response)
88
+ JSON.parse(http_response.body)
89
+ end
90
+
91
+
92
+ def to_s
93
+ self.class.name + ": endpoint = #{@url}, client = #{@client}"
94
+ end
95
+
96
+
97
+ def ==(other)
98
+ client.equal?(other.client) && url == other.url && id_field == other.id_field
99
+ end
100
+
101
+ private
102
+
103
+ def get_headers(specify_content_type)
104
+ create_headers(specify_content_type, @client.auth_token)
105
+ end
106
+ end
107
+
108
+ end
@@ -0,0 +1,35 @@
1
+ module SisRuby
2
+
3
+ class BadResponseError < RuntimeError
4
+
5
+ attr_reader :response
6
+
7
+ def initialize(response)
8
+ @response = response
9
+ end
10
+
11
+
12
+ def to_s
13
+
14
+ first_header_line = if response && response.options[:response_headers]
15
+ header_lines = response.options[:response_headers].split("\r\n")
16
+ header_lines.any? ? header_lines.first : nil
17
+ else
18
+ nil
19
+ end
20
+
21
+ body = if response && response.options[:response_body]
22
+ response.options[:response_body].rstrip
23
+ else
24
+ nil
25
+ end
26
+
27
+ code = response.code
28
+
29
+ string = "#{self.class.name}: #{code}"
30
+ string << ": #{body}" if body
31
+ string << (first_header_line ? " (#{first_header_line})" : '')
32
+ string
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,12 @@
1
+ module SisRuby
2
+ class MissingIdError < RuntimeError
3
+
4
+ def initialize(id_field_name)
5
+ @id_field_name = id_field_name
6
+ end
7
+
8
+ def to_s
9
+ "Missing required id field #{@id_field_name}}"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'exceptions/bad_response_error'
2
+
3
+
4
+ # Methods for helping get data from SIS.
5
+ module SisRuby
6
+ module GetHelper
7
+
8
+ # Creates the header for a request.
9
+ def create_headers(specify_content_type, auth_token = nil)
10
+ headers = {
11
+ 'Accept' => 'application/json'
12
+ }
13
+
14
+ if auth_token
15
+ headers['x-auth-token'] = auth_token
16
+ end
17
+
18
+ if specify_content_type
19
+ headers['Content-Type'] = 'application/json'
20
+ end
21
+
22
+ headers
23
+ end
24
+
25
+
26
+ # Raises an error on response failure.
27
+ def validate_response_success(response)
28
+ unless response.code.between?(200, 299)
29
+ raise BadResponseError.new(response)
30
+ end
31
+ end
32
+
33
+
34
+ # Returns a Typhoeus response.
35
+ def typhoeus_get(query)
36
+ # TODO: Simplify w/Typhoeus.get ?
37
+ Typhoeus::Request.new(url, params: query, headers: get_headers(true) ).run
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+
2
+ # Takes methods and builds an internal hash where the method calls
3
+ # are keys, and their parameter is the value.
4
+ class HashBuilder
5
+
6
+ attr_reader :key_type
7
+
8
+ def initialize(key_type = String)
9
+ raise ArgumentError.new("Invalid key type '#{key_type}'") unless [String, Symbol].include?(key_type)
10
+ @key_type = key_type
11
+ @data = {}
12
+ end
13
+
14
+
15
+ def method_missing(method_name, *args)
16
+ value = args.first
17
+ key = (@key_type == String) ? method_name.to_s : method_name.to_sym
18
+ @data[key] = value
19
+ self
20
+ end
21
+
22
+
23
+ def respond_to_missing?(method_name, include_private)
24
+ true # TODO: exclude ancestor methods?
25
+ end
26
+
27
+
28
+ def to_h
29
+ @data
30
+ end
31
+ end