orchestrator_api 0.0.2

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c38a1f1dd4cb1d0fd573379098051f4eabfb8590
4
+ data.tar.gz: 1400ae44d04625a0e6ef680f8edf3cc0b7b76c01
5
+ SHA512:
6
+ metadata.gz: 70d8f68a0b2801b5cc3cf850874159ec910ba259a9b71af3ac8cd9f7dd3d154ab9573fdbf248a120ad7385eef25f1811ba166bfbd1e8e890b14e92199340bae1
7
+ data.tar.gz: ab88fb43301ccb59114b8d10441662163115e2127fe7402654f5330c55039875583d3cf29cb911ca72ef74e21cfe0c633fdd5841dd14e16b5ea4c09dbf06ade5
@@ -0,0 +1,46 @@
1
+ # How to contribute
2
+
3
+ * Make sure you have a [GitHub account](https://github.com/signup/free)
4
+ * Fork the repository on GitHub
5
+
6
+ ## Making Changes
7
+
8
+ * Create a topic branch from where you want to base your work (this is almost
9
+ definitely the master branch).
10
+ * To quickly create a topic branch based on master; `git branch
11
+ fix/master/my_contribution master` then checkout the new branch with `git
12
+ checkout fix/master/my_contribution`.
13
+ * Please avoid working directly on the
14
+ `master` branch.
15
+ * Make commits of logical units.
16
+ * Check for unnecessary whitespace with `git diff --check` before committing.
17
+ * Make sure your commit messages are in the proper format.
18
+
19
+ ````
20
+ Make the example in CONTRIBUTING imperative and concrete
21
+
22
+ Without this patch applied the example commit message in the CONTRIBUTING
23
+ document is not a concrete example. This is a problem because the
24
+ contributor is left to imagine what the commit message should look like
25
+ based on a description rather than an example. This patch fixes the
26
+ problem by making the example concrete and imperative.
27
+
28
+ The first line is a real life imperative statement with a ticket number
29
+ from our issue tracker. The body describes the behavior without the patch,
30
+ why this is a problem, and how the patch fixes the problem when applied.
31
+ ````
32
+
33
+ * Make sure you have added the necessary tests for your changes.
34
+ * Run _all_ the tests to assure nothing else was accidentally broken.
35
+
36
+ ## Submitting Changes
37
+
38
+ * Sign the [Contributor License Agreement](http://links.puppet.com/cla).
39
+ * Push your changes to a topic branch in your fork of the repository.
40
+ * Submit a pull request to the repository in the puppetlabs organization.
41
+
42
+ # Additional Resources
43
+
44
+ * [Contributor License Agreement](http://links.puppet.com/cla)
45
+ * [General GitHub documentation](http://help.github.com/)
46
+ * [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rspec'
4
+ gem 'webmock'
data/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ orchestrator_api
2
+
3
+ Copyright (C) 2016 Puppet Labs Inc
4
+
5
+ Puppet,Inc. can be contacted at: info@puppet.com
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
@@ -0,0 +1,75 @@
1
+ # Orchestrator_api
2
+
3
+ A simple client for interacting with the Orchestration Services API in Puppet Enterprise
4
+ [Puppet orchestration API](https://docs.puppet.com/pe/latest/api_index.html#puppet-orchestrator-api)
5
+
6
+ ## Compatibility
7
+
8
+ Currently, this client supports the "V1" endpoints shipped as part of Puppet Enterprise 2016.2.
9
+
10
+ ## Installation
11
+
12
+ ```shell
13
+ gem install orchestrator_api
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ Requires a token with 'Orchestration' permissions. By default the token is
19
+ expected to be at `~/.puppetlabs/token` which is the default location used by
20
+ `puppet-access` when creating token.
21
+
22
+ ### initialization Settings
23
+
24
+ * `service-url` **[required]** - Base URL for the location of the Orchestrator API service
25
+ * `ca_cert` **[required]** - Path to the CA certificate file needed to verify the SSL connection to the API.
26
+ * `token_path`- Path to a file with the RBAC token in it (defaults to `~/.puppetlabs/token`)
27
+ * `token` - Pass directly the RBAC token, if specified the token will be used instead of a token from file.
28
+
29
+ ### Example
30
+
31
+ ```ruby
32
+ require 'orchestrator_api'
33
+
34
+ # Create a new client
35
+ # Requires at least a server name and path to the CA certificate
36
+
37
+ client = Orchestrator_api.new({
38
+ 'service-url' => 'https://orchestrator.example.lan:8143/orchestrator/v1',
39
+ 'ca_cert' => '/path/to/cert'
40
+ })
41
+
42
+ ## Access endpoints through the client object
43
+
44
+ # Get details on all known jobs
45
+ result = client.jobs.all
46
+
47
+ # Get details on Individual jobs (job "5" in this example)
48
+ client.jobs.details(5)
49
+
50
+ # Perform an orchestrator deployment
51
+ new_job_details = client.command.deploy('production', {'noop' => true })
52
+ ```
53
+
54
+ ## Tests
55
+
56
+ ```shell
57
+ bundle install
58
+ bundle exec rspec
59
+ ```
60
+
61
+ ## Issues & Contributions
62
+
63
+ File issues or feature requests using [GitHub
64
+ issues](https://github.com/puppetlabs/orchestrator_api-ruby/issues).
65
+
66
+ If you are interested in contributing to this project, please see the
67
+ [Contribution Guidelines](CONTRIBUTING.md)
68
+
69
+ ## Authors
70
+
71
+ Tom Linkin <tom@puppet.com>
72
+
73
+ ## License
74
+
75
+ See LICENSE.
@@ -0,0 +1,94 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'openssl'
5
+
6
+ class Orchestrator_api
7
+ require 'orchestrator_api/error'
8
+ require 'orchestrator_api/command'
9
+ require 'orchestrator_api/jobs'
10
+ require 'orchestrator_api/environments'
11
+
12
+ attr_accessor :config, :token
13
+
14
+ def initialize(settings = {})
15
+
16
+ @config = { 'token_path' => File.join(Dir.home, '.puppetlabs', 'token'),
17
+ }.merge(settings)
18
+
19
+ if @config['token']
20
+ @token = @config['token']
21
+ else
22
+ @token = File.read(@config['token_path'])
23
+ end
24
+
25
+ if @config['service-url'].nil?
26
+ raise "Configuration error: 'service-url' must specify the server running the Orchestration services and cannot be empty"
27
+ end
28
+ if @config['ca_cert'].nil?
29
+ raise "Configuration error: 'ca_cert' must specify a path to the CA certificate used for communications with the server and cannot be empty"
30
+ end
31
+ end
32
+
33
+ def url
34
+ config['service-url']
35
+ end
36
+
37
+ def create_http(uri)
38
+ http = Net::HTTP.new(uri.host, uri.port)
39
+ http.use_ssl = true
40
+ http.ssl_version = :TLSv1
41
+ http.ca_file = config['ca_cert']
42
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
43
+ http
44
+ end
45
+
46
+ def get(location)
47
+ uri = URI.parse(location)
48
+ https = create_http(uri)
49
+
50
+ req = Net::HTTP::Get.new(uri.request_uri)
51
+ req['Content-Type'] = "application/json"
52
+ req.add_field('X-Authentication', token)
53
+ res = https.request(req)
54
+
55
+ if res.code != "200"
56
+ raise Orchestrator_api::Error.make_error_from_response(res)
57
+ end
58
+
59
+ JSON.parse(res.body)
60
+ end
61
+
62
+ def post(location, body)
63
+ uri = URI.parse(location)
64
+ https = create_http(uri)
65
+
66
+ req = Net::HTTP::Post.new(uri.request_uri)
67
+ req['Content-Type'] = "application/json"
68
+ req.add_field('X-Authentication', token)
69
+ req.body = body.to_json
70
+ res = https.request(req)
71
+
72
+ if res.code != "202"
73
+ raise Orchestrator_api::Error.make_error_from_response(res)
74
+ end
75
+
76
+ JSON.parse(res.body)
77
+ end
78
+
79
+ def command
80
+ @command ||= Orchestrator_api::Command.new(self, url)
81
+ end
82
+
83
+ def environments
84
+ @environments ||= Orchestrator_api::Environments.new(self, url)
85
+ end
86
+
87
+ def jobs
88
+ @jobs ||= Orchestrator_api::Jobs.new(self, url)
89
+ end
90
+
91
+ def root
92
+ get(url)
93
+ end
94
+ end
@@ -0,0 +1,18 @@
1
+ class Orchestrator_api::Command
2
+
3
+ def initialize(https, api_url_base)
4
+ @https = https
5
+ @api_url_base = api_url_base
6
+ end
7
+
8
+ def deploy(environment, options = {})
9
+ raise ArgumentError, 'Must pass options as a hash' unless options.is_a? Hash
10
+ options['environment'] = environment
11
+ @https.post("#{@api_url_base}/command/deploy", options)
12
+ end
13
+
14
+ def stop(job_number)
15
+ data = {"job" => "#{job_number}"}
16
+ @https.post("#{@api_url_base}/command/stop",data)
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ class Orchestrator_api::Environments
2
+
3
+ def initialize(https, api_url_base)
4
+ @https = https
5
+ @api_url_base = api_url_base
6
+ end
7
+
8
+ def all
9
+ @https.get("#{api_url_base}/environments")
10
+ end
11
+
12
+ def applications(id)
13
+ @https.get("#{api_url_base}/environments/#{id}/applications")
14
+ end
15
+
16
+ def instances(id)
17
+ @https.get("#{api_url_base}/environments/#{id}/instances")
18
+ end
19
+ end
@@ -0,0 +1,56 @@
1
+
2
+ class Orchestrator_api::Error < Exception
3
+
4
+ def initialize(data,code)
5
+ @code = code
6
+ @kind = data['kind']
7
+ @details = data['details']
8
+ super(data['msg'])
9
+ end
10
+
11
+ def self.make_error_from_response(res)
12
+ begin
13
+ data = JSON.parse(res.body)
14
+ rescue
15
+ return EndpointError.new("An unspecified error has occurred with the Orchestrator API")
16
+ end
17
+ code = res.code
18
+
19
+ case data['kind']
20
+ when 'puppetlabs.validators/validation-error'
21
+ ValidationError.new(data, code)
22
+ when 'puppetlabs.orchestrator/unknown-job'
23
+ UnknownJob.new(data, code)
24
+ when 'puppetlabs.orchestrator/unknown-environment'
25
+ UnknownEnvironment.new(data, code)
26
+ when 'puppetlabs.orchestrator/empty-environment'
27
+ EmptyEnvironment.new(data, code)
28
+ when 'puppetlabs.orchestrator/empty-target'
29
+ EmptyTarget.new(data, code)
30
+ when 'puppetlabs.orchestrator/dependency-cycle'
31
+ DependencyCycle.new(data, code)
32
+ when 'puppetlabs.orchestrator/puppetdb-error'
33
+ PuppetdbError.new(data, code)
34
+ when 'puppetlabs.orchestrator/query-error'
35
+ QueryError.new(data, code)
36
+ when 'puppetlabs.orchestrator/unknown-error'
37
+ UnknownError.new(data, code)
38
+ when 'puppetlabs.orchestrator/not-permitted'
39
+ UnauthorizedError.new(data, code)
40
+ else
41
+ EndpointError.new("An unspecified error has occurred with the Orchestrator API")
42
+ end
43
+ end
44
+ end
45
+
46
+ class Orchestrator_api::Error::ValidationError < Orchestrator_api::Error; end
47
+ class Orchestrator_api::Error::UnknownJob < Orchestrator_api::Error; end
48
+ class Orchestrator_api::Error::UnknownEnvironment < Orchestrator_api::Error; end
49
+ class Orchestrator_api::Error::EmptyEnvironment < Orchestrator_api::Error; end
50
+ class Orchestrator_api::Error::EmptyTarget < Orchestrator_api::Error; end
51
+ class Orchestrator_api::Error::DependencyCycle < Orchestrator_api::Error; end
52
+ class Orchestrator_api::Error::PuppetdbError < Orchestrator_api::Error; end
53
+ class Orchestrator_api::Error::QueryError < Orchestrator_api::Error; end
54
+ class Orchestrator_api::Error::UnknownError < Orchestrator_api::Error; end
55
+ class Orchestrator_api::Error::UnauthorizedError < Orchestrator_api::Error; end
56
+ class Orchestrator_api::Error::EndpointError < Exception; end
@@ -0,0 +1,37 @@
1
+ class Orchestrator_api::Jobs
2
+
3
+ def initialize(http, api_url_base)
4
+ @https = http
5
+ @api_url_base = api_url_base
6
+ end
7
+
8
+ def all(limit=nil)
9
+ url = "#{@api_url_base}/jobs"
10
+ if limit
11
+ url << "?limit=#{limit}"
12
+ end
13
+
14
+ @https.get(url)
15
+ end
16
+
17
+ def details(id)
18
+ @https.get("#{@api_url_base}/jobs/#{id}")
19
+ end
20
+
21
+ def nodes(id)
22
+ @https.get("#{@api_url_base}/jobs/#{id}/nodes")
23
+ end
24
+
25
+ def report(id)
26
+ @https.get("#{@api_url_base}/jobs/#{id}/report")
27
+ end
28
+
29
+ def events(id, start = nil)
30
+ url = "#{@api_url_base}/jobs/#{id}/events"
31
+ if start
32
+ url << "?start=#{start}"
33
+ end
34
+
35
+ @https.get(url)
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'orchestrator_api'
3
+ s.version = '0.0.2'
4
+ s.summary = "Simple Ruby client library for PE Orchestration Services"
5
+ s.authors = "Thomas Linkin"
6
+ s.email = 'info@puppet.com'
7
+ s.files = `git ls-files`.split($/)
8
+ s.homepage = 'https://github.com/puppetlabs/orchestrator_api-ruby'
9
+ s.license = "apache"
10
+ s.require_paths = ["lib"]
11
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/orchestrator_api'
3
+
4
+ describe Orchestrator_api do
5
+
6
+ before(:all) do
7
+ config = {
8
+ 'service-url' => 'https://orchestrator.example.lan:8143/orchestrator/v1',
9
+ 'ca_cert' => '/etc/puppetlabs/puppet/ssl/certs/ca.pem',
10
+ 'token' => 'myfaketoken'
11
+ }
12
+
13
+ @orchestrator_api = Orchestrator_api.new(config)
14
+ end
15
+
16
+ describe "#newobject" do
17
+ it "takes a configuration hash and returns a Orchestrator_api object" do
18
+ expect(@orchestrator_api).to be_an_instance_of Orchestrator_api
19
+ end
20
+
21
+
22
+ it "has methods with objects that are not nil" do
23
+ expect(@orchestrator_api.command).to be_truthy
24
+ expect(@orchestrator_api.jobs).to be_truthy
25
+ expect(@orchestrator_api.environments).to be_truthy
26
+ end
27
+
28
+ it "complains when a configuration value for 'server' is not provided" do
29
+ config = {
30
+ 'ca_cert' => '/etc/puppetlabs/puppet/ssl/certs/ca.pem',
31
+ 'token' => 'myfaketoken'
32
+ }
33
+
34
+ expect{ Orchestrator_api.new(config) }.to raise_error("Configuration error: 'service-url' must specify the server running the Orchestration services and cannot be empty")
35
+ end
36
+
37
+ it "complains when a configuration value for 'ca_certificate_path' is not provided" do
38
+ config = {
39
+ 'service-url' => 'https://orchestrator.example.lan:8143/orchestrator/v1',
40
+ 'token' => 'myfaketoken'
41
+ }
42
+
43
+ expect{ Orchestrator_api.new(config) }.to raise_error("Configuration error: 'ca_cert' must specify a path to the CA certificate used for communications with the server and cannot be empty")
44
+ end
45
+ end
46
+
47
+ describe "#get" do
48
+ it 'takes an endpoint and parses the response as JSON' do
49
+ stub_request(:get, "https://orchestrator.example.lan:8143/endpoint").
50
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
51
+ to_return(:status => 200, :body => "{}", :headers => {})
52
+
53
+ expect(@orchestrator_api.get('https://orchestrator.example.lan:8143/endpoint')).to be_an_instance_of Hash
54
+ end
55
+ end
56
+
57
+ describe "#post" do
58
+ it 'takes an endpoint, endpoint, and parses the response as JSON' do
59
+ stub_request(:post, "https://orchestrator.example.lan:8143/endpoint").
60
+ with( :body => "{\"data\":\"atad\"}",
61
+ :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
62
+ to_return(:status => 202, :body => "{}", :headers => {})
63
+
64
+ expect(@orchestrator_api.post('https://orchestrator.example.lan:8143/endpoint',{'data' => 'atad'})).to be_an_instance_of Hash
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,2 @@
1
+ require 'webmock/rspec'
2
+ require 'orchestrator_api'
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe Orchestrator_api::Command do
4
+
5
+ before :each do
6
+ @url_base = "https://master.puppetlabs.vm:8143/orchestrator/v1"
7
+
8
+ config = {
9
+ 'service-url' => 'https://orchestrator.example.lan:8143/orchestrator/v1',
10
+ 'ca_cert' => '/etc/puppetlabs/puppet/ssl/certs/ca.pem',
11
+ 'token' => 'myfaketoken'
12
+ }
13
+
14
+ @orchestrator = Orchestrator_api.new(config)
15
+ end
16
+
17
+ describe '#newobject' do
18
+ it 'takes an orchestrator url and Orchestrator_api object and returns a Orchestrator_api::Command object' do
19
+ expect(@orchestrator.command).to be_an_instance_of Orchestrator_api::Command
20
+ end
21
+ end
22
+
23
+ describe '#deploy' do
24
+ it 'takes an environment and issues a deploy command' do
25
+ response = "{\n \"job\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1\",\n \"name\" : \"1\"\n }\n}"
26
+
27
+ stub_request(:post, "https://orchestrator.example.lan:8143/orchestrator/v1/command/deploy").
28
+ with(:body => "{\"environment\":\"production\"}",
29
+ :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
30
+ to_return(:status => 202, :body => response, :headers => {})
31
+
32
+ expect(@orchestrator.command.deploy('production')).to be_an_instance_of Hash
33
+ end
34
+
35
+ it 'takes an environment and hash of additional details' do
36
+ response = "{\n \"job\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1\",\n \"name\" : \"1\"\n }\n}"
37
+
38
+ stub_request(:post, "https://orchestrator.example.lan:8143/orchestrator/v1/command/deploy").
39
+ with(:body => "{\"scope\":{\"nodes\":[\"node1.example.lan\",\"node2.example.lan\"]},\"environment\":\"production\"}",
40
+ :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
41
+ to_return(:status => 202, :body => response, :headers => {})
42
+
43
+ details = {
44
+ 'scope' => {
45
+ 'nodes' => ['node1.example.lan','node2.example.lan']
46
+ }
47
+ }
48
+ expect(@orchestrator.command.deploy('production',details)).to be_an_instance_of Hash
49
+ end
50
+ end
51
+
52
+ describe '#stop' do
53
+ it 'takes a valid job number and stops the job' do
54
+ response = "{\n \"job\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1\",\n \"name\" : \"1\",\n \"nodes\" : {\n \"finished\" : 3\n }\n }\n}"
55
+
56
+ stub_request(:post, "https://orchestrator.example.lan:8143/orchestrator/v1/command/stop").
57
+ with(:body => "{\"job\":\"1\"}",
58
+ :headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
59
+ to_return(:status => 202, :body => response, :headers => {})
60
+
61
+ expect(@orchestrator.command.stop(1)).to be_an_instance_of Hash
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Orchestrator_api::Environments do
4
+
5
+ before :each do
6
+ config = {
7
+ 'service-url' => 'https://orchestrator.example.lan:8143/orchestrator/v1',
8
+ 'ca_cert' => '/etc/puppetlabs/puppet/ssl/certs/ca.pem',
9
+ 'token' => 'myfaketoken'
10
+ }
11
+
12
+ @orchestrator = Orchestrator_api.new(config)
13
+ end
14
+
15
+ describe '#newobject' do
16
+ it 'takes an orchestrator url and Orchestrator_api object and returns a Orchestrator_api::Environment object' do
17
+ expect(@orchestrator.environments).to be_an_instance_of Orchestrator_api::Environments
18
+ end
19
+ end
20
+
21
+ describe '#all' do
22
+ end
23
+
24
+ describe '#applications' do
25
+ end
26
+
27
+ describe '#instances' do
28
+ end
29
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+
3
+ describe Orchestrator_api::Error do
4
+
5
+ it 'is an child class of Exception' do
6
+ expect(Orchestrator_api::Error).to be <= Exception
7
+ end
8
+
9
+ %w(ValidationError UnknownJob UnknownEnvironment EmptyEnvironment EmptyTarget DependencyCycle PuppetdbError QueryError UnknownError UnauthorizedError).each do |name|
10
+ describe "::#{name}" do
11
+ klass = Orchestrator_api::Error.const_get(name)
12
+ it "should be a child class of Orchestrator_api::Error" do
13
+ expect(klass).to be <= Orchestrator_api::Error
14
+ end
15
+ end
16
+ end
17
+
18
+ describe "::make_error_from_response" do
19
+ before(:all) do
20
+ class FakeResponse
21
+ attr_reader :code, :body
22
+ def initialize(error)
23
+ @code = '400'
24
+ @body = "{\"kind\":\"#{error}\"}"
25
+ end
26
+ end
27
+ end
28
+
29
+ %w(
30
+ puppetlabs.validators/validation-error:ValidationError
31
+ puppetlabs.orchestrator/unknown-job:UnknownJob
32
+ puppetlabs.orchestrator/unknown-environment:UnknownEnvironment
33
+ puppetlabs.orchestrator/empty-target:EmptyTarget
34
+ puppetlabs.orchestrator/dependency-cycle:DependencyCycle
35
+ puppetlabs.orchestrator/puppetdb-error:PuppetdbError
36
+ puppetlabs.orchestrator/query-error:QueryError
37
+ puppetlabs.orchestrator/unknown-error:UnknownError
38
+ puppetlabs.orchestrator/not-permitted:UnauthorizedError
39
+ ).each do |item|
40
+ error = item.split(':')
41
+ it "creates an exception based on an \"#{error.first}\" error response from the server" do
42
+ res = FakeResponse.new(error.first)
43
+ klass = Orchestrator_api::Error.const_get(error[1])
44
+ expect( Orchestrator_api::Error.make_error_from_response(res) ).to be_an_instance_of klass
45
+ end
46
+ end
47
+
48
+ it "creates an Orchestrator_api::Error::EndpointError exception when the response from the server isn't parsable JSON" do
49
+ res = FakeResponse.new("makesforunparsablejson\"")
50
+ expect( Orchestrator_api::Error.make_error_from_response(res)).to be_an_instance_of Orchestrator_api::Error::EndpointError
51
+ end
52
+
53
+ it "creates an Orchestrator_api::Error::EndpointError exception when it does not understand the error from the service" do
54
+ res = FakeResponse.new('notarealerror')
55
+ expect( Orchestrator_api::Error.make_error_from_response(res)).to be_an_instance_of Orchestrator_api::Error::EndpointError
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe Orchestrator_api::Jobs do
4
+
5
+ before :each do
6
+ config = {
7
+ 'service-url' => 'https://orchestrator.example.lan:8143/orchestrator/v1',
8
+ 'ca_cert' => '/etc/puppetlabs/puppet/ssl/certs/ca.pem',
9
+ 'token' => 'myfaketoken'
10
+ }
11
+
12
+ @orchestrator = Orchestrator_api.new(config)
13
+ end
14
+
15
+ describe '#newobject' do
16
+ it 'takes an orchestrator url and Orchestrator_api object and returns a Orchestrator_api::Jobs object' do
17
+ expect(@orchestrator.jobs).to be_an_instance_of Orchestrator_api::Jobs
18
+ end
19
+ end
20
+
21
+ describe '#all' do
22
+ response = "{\n \"items\" : [ {\n \"report\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58/report\"\n },\n \"name\" : \"58\",\n \"events\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58/events\"\n },\n \"state\" : \"failed\",\n \"nodes\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58/nodes\"\n },\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58\",\n \"environment\" : {\n \"name\" : \"production\"\n },\n \"options\" : {\n \"concurrency\" : null,\n \"noop\" : false,\n \"trace\" : false,\n \"debug\" : false,\n \"scope\" : {\n \"whole_environment\" : true\n },\n \"enforce_environment\" : true,\n \"environment\" : \"production\",\n \"evaltrace\" : false,\n \"target\" : \"whole environment\"\n },\n \"timestamp\" : \"2016-09-20T19:01:18Z\",\n \"owner\" : {\n \"id\" : \"80f8475c-d8b5-4dfd-b567-3cae4ef7a3a7\",\n \"login\" : \"tom\"\n },\n \"node_count\" : 2\n } ]\n}"
23
+
24
+ it 'optionally accepts a number to limit results' do
25
+ stub_request(:get, "https://orchestrator.example.lan:8143/orchestrator/v1/jobs?limit=1").
26
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
27
+ to_return(:status => 200, :body => response, :headers => {})
28
+
29
+ expect(@orchestrator.jobs.all(1)).to be_an_instance_of Hash
30
+ end
31
+
32
+ it 'can return results without a limit' do
33
+ response = "{\n \"items\" : [ {\n \"report\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58/report\"\n },\n \"name\" : \"58\",\n \"events\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58/events\"\n },\n \"state\" : \"failed\",\n \"nodes\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58/nodes\"\n },\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/58\",\n \"environment\" : {\n \"name\" : \"production\"\n },\n \"options\" : {\n \"concurrency\" : null,\n \"noop\" : false,\n \"trace\" : false,\n \"debug\" : false,\n \"scope\" : {\n \"whole_environment\" : true\n },\n \"enforce_environment\" : true,\n \"environment\" : \"production\",\n \"evaltrace\" : false,\n \"target\" : \"whole environment\"\n },\n \"timestamp\" : \"2016-09-20T19:01:18Z\",\n \"owner\" : {\n \"id\" : \"80f8475c-d8b5-4dfd-b567-3cae4ef7a3a7\",\n \"login\" : \"tom\"\n },\n \"node_count\" : 2\n } ]\n}"
34
+
35
+ stub_request(:get, "https://orchestrator.example.lan:8143/orchestrator/v1/jobs").
36
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
37
+ to_return(:status => 200, :body => response, :headers => {})
38
+
39
+ expect(@orchestrator.jobs.all).to be_an_instance_of Hash
40
+ end
41
+ end
42
+
43
+ describe '#details' do
44
+ end
45
+
46
+ describe '#nodes' do
47
+ end
48
+
49
+ describe '#report' do
50
+ end
51
+
52
+ describe '#events' do
53
+ response = "{\n \"next-events\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1/events?start=7\"\n },\n \"items\" : [ {\n \"type\" : \"node_finished\",\n \"timestamp\" : \"2016-08-15T21:35:26Z\",\n \"details\" : {\n \"node\" : \"orchestrator.example.lan\",\n \"detail\" : {\n \"hash\" : \"c02e060e07da2a0e3ecdbad0e46a265ca22f433d\",\n \"noop\" : false,\n \"status\" : \"unchanged\",\n \"metrics\" : {\n \"corrective_change\" : 0,\n \"out_of_sync\" : 3,\n \"restarted\" : 0,\n \"skipped\" : 0,\n \"total\" : 789,\n \"changed\" : 0,\n \"scheduled\" : 0,\n \"failed_to_restart\" : 0,\n \"failed\" : 0\n },\n \"report-url\" : \"https://orchestrator.example.lan/#/cm/report/c02e060e07da2a0e3ecdbad0e46a265ca22f433d\",\n \"environment\" : \"production\",\n \"configuration_version\" : \"1471296883\"\n }\n },\n \"message\" : \"Finished puppet run on orchestrator.example.lan - Success! \",\n \"id\" : \"6\"\n } ]\n}"
54
+
55
+ it 'takes a job number and accespts a start number to retrieve latest results' do
56
+ stub_request(:get, "https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1/events?start=10").
57
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
58
+ to_return(:status => 200, :body => response, :headers => {})
59
+
60
+ expect(@orchestrator.jobs.events(1,10)).to be_an_instance_of Hash
61
+ end
62
+ it 'takes a job number but does not need a start number' do
63
+ response = "{\n \"next-events\" : {\n \"id\" : \"https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1/events?start=7\"\n },\n \"items\" : [ {\n \"type\" : \"node_running\",\n \"timestamp\" : \"2016-08-15T21:34:34Z\",\n \"details\" : {\n \"node\" : \"orchestrator.example.lan\",\n \"detail\" : {\n \"noop\" : false\n }\n },\n \"message\" : \"Started puppet run on orchestrator.example.lan ...\",\n \"id\" : \"1\"\n }, {\n \"type\" : \"node_running\",\n \"timestamp\" : \"2016-08-15T21:34:34Z\",\n \"details\" : {\n \"node\" : \"agent2.puppetlabs.vm\",\n \"detail\" : {\n \"noop\" : false\n }\n },\n \"message\" : \"Started puppet run on agent2.puppetlabs.vm ...\",\n \"id\" : \"2\"\n }, {\n \"type\" : \"node_running\",\n \"timestamp\" : \"2016-08-15T21:34:34Z\",\n \"details\" : {\n \"node\" : \"agent1.puppetlabs.vm\",\n \"detail\" : {\n \"noop\" : false\n }\n },\n \"message\" : \"Started puppet run on agent1.puppetlabs.vm ...\",\n \"id\" : \"3\"\n }, {\n \"type\" : \"node_finished\",\n \"timestamp\" : \"2016-08-15T21:34:47Z\",\n \"details\" : {\n \"node\" : \"agent1.puppetlabs.vm\",\n \"detail\" : {\n \"hash\" : \"20d60265af8bafc0ac1b99c1eb4b8bde411a81a3\",\n \"noop\" : false,\n \"status\" : \"unchanged\",\n \"metrics\" : {\n \"corrective_change\" : 0,\n \"out_of_sync\" : 0,\n \"restarted\" : 0,\n \"skipped\" : 0,\n \"total\" : 171,\n \"changed\" : 0,\n \"scheduled\" : 0,\n \"failed_to_restart\" : 0,\n \"failed\" : 0\n },\n \"report-url\" : \"https://orchestrator.example.lan/#/cm/report/20d60265af8bafc0ac1b99c1eb4b8bde411a81a3\",\n \"environment\" : \"production\",\n \"configuration_version\" : \"1471296882\"\n }\n },\n \"message\" : \"Finished puppet run on agent1.puppetlabs.vm - Success! \",\n \"id\" : \"4\"\n }, {\n \"type\" : \"node_finished\",\n \"timestamp\" : \"2016-08-15T21:34:47Z\",\n \"details\" : {\n \"node\" : \"agent2.puppetlabs.vm\",\n \"detail\" : {\n \"hash\" : \"d460f0a513b1e18d43f19867b269cf105772fc41\",\n \"noop\" : false,\n \"status\" : \"unchanged\",\n \"metrics\" : {\n \"corrective_change\" : 0,\n \"out_of_sync\" : 0,\n \"restarted\" : 0,\n \"skipped\" : 0,\n \"total\" : 171,\n \"changed\" : 0,\n \"scheduled\" : 0,\n \"failed_to_restart\" : 0,\n \"failed\" : 0\n },\n \"report-url\" : \"https://orchestrator.example.lan/#/cm/report/d460f0a513b1e18d43f19867b269cf105772fc41\",\n \"environment\" : \"production\",\n \"configuration_version\" : \"1471296882\"\n }\n },\n \"message\" : \"Finished puppet run on agent2.puppetlabs.vm - Success! \",\n \"id\" : \"5\"\n }, {\n \"type\" : \"node_finished\",\n \"timestamp\" : \"2016-08-15T21:35:26Z\",\n \"details\" : {\n \"node\" : \"orchestrator.example.lan\",\n \"detail\" : {\n \"hash\" : \"c02e060e07da2a0e3ecdbad0e46a265ca22f433d\",\n \"noop\" : false,\n \"status\" : \"unchanged\",\n \"metrics\" : {\n \"corrective_change\" : 0,\n \"out_of_sync\" : 3,\n \"restarted\" : 0,\n \"skipped\" : 0,\n \"total\" : 789,\n \"changed\" : 0,\n \"scheduled\" : 0,\n \"failed_to_restart\" : 0,\n \"failed\" : 0\n },\n \"report-url\" : \"https://orchestrator.example.lan/#/cm/report/c02e060e07da2a0e3ecdbad0e46a265ca22f433d\",\n \"environment\" : \"production\",\n \"configuration_version\" : \"1471296883\"\n }\n },\n \"message\" : \"Finished puppet run on orchestrator.example.lan - Success! \",\n \"id\" : \"6\"\n } ]\n}"
64
+
65
+ stub_request(:get, "https://orchestrator.example.lan:8143/orchestrator/v1/jobs/1/events").
66
+ with(:headers => {'Accept'=>'*/*', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Content-Type'=>'application/json', 'User-Agent'=>'Ruby', 'X-Authentication'=>'myfaketoken'}).
67
+ to_return(:status => 200, :body => response, :headers => {})
68
+
69
+ expect(@orchestrator.jobs.events(1)).to be_an_instance_of Hash
70
+ end
71
+ end
72
+ end
73
+
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orchestrator_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Linkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: info@puppet.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - CONTRIBUTING.md
20
+ - Gemfile
21
+ - LICENSE
22
+ - README.md
23
+ - lib/orchestrator_api.rb
24
+ - lib/orchestrator_api/command.rb
25
+ - lib/orchestrator_api/environments.rb
26
+ - lib/orchestrator_api/error.rb
27
+ - lib/orchestrator_api/jobs.rb
28
+ - orchestrator_api-ruby.gemspec
29
+ - spec/orchestrator_api_spec.rb
30
+ - spec/spec_helper.rb
31
+ - spec/unit/command_spec.rb
32
+ - spec/unit/environment_spec.rb
33
+ - spec/unit/error_spec.rb
34
+ - spec/unit/jobs_spec.rb
35
+ homepage: https://github.com/puppetlabs/orchestrator_api-ruby
36
+ licenses:
37
+ - apache
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.2.3
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Simple Ruby client library for PE Orchestration Services
59
+ test_files: []