cyclid-client 0.3.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.
@@ -0,0 +1,84 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'colorize'
16
+
17
+ # Cyclid top level module
18
+ module Cyclid
19
+ module Cli
20
+ # 'user' sub-command
21
+ class User < Thor
22
+ desc 'show', 'Show your user details'
23
+ def show
24
+ user = client.user_get(client.config.username)
25
+
26
+ # Pretty print the user details
27
+ puts 'Username: '.colorize(:cyan) + user['username']
28
+ puts 'Name: '.colorize(:cyan) + (user['name'] || '')
29
+ puts 'Email: '.colorize(:cyan) + user['email']
30
+ puts 'Organizations'.colorize(:cyan)
31
+ if user['organizations'].any?
32
+ user['organizations'].each do |org|
33
+ puts "\t#{org}"
34
+ end
35
+ else
36
+ puts "\tNone"
37
+ end
38
+ rescue StandardError => ex
39
+ abort "Failed to get user: #{ex}"
40
+ end
41
+
42
+ desc 'modify', 'Modify your user'
43
+ long_desc <<-LONGDESC
44
+ Modify your user details.
45
+
46
+ The --email option sets your email address.
47
+
48
+ The --password option sets an encrypted password for HTTP Basic authentication and Cyclid
49
+ UI console logins.
50
+
51
+ The --secret option sets a shared secret which is used for signing Cyclid API requests.
52
+ LONGDESC
53
+ option :email, aliases: '-e'
54
+ option :password, aliases: '-p'
55
+ option :secret, aliases: '-s'
56
+ def modify
57
+ client.user_modify(client.config.username,
58
+ email: options[:email],
59
+ password: options[:password],
60
+ secret: options[:secret])
61
+ rescue StandardError => ex
62
+ abort "Failed to modify user: #{ex}"
63
+ end
64
+
65
+ desc 'passwd', 'Change your password'
66
+ def passwd
67
+ # Get the new password
68
+ print 'Password: '
69
+ password = STDIN.noecho(&:gets).chomp
70
+ print "\nConfirm password: "
71
+ confirm = STDIN.noecho(&:gets).chomp
72
+ print "\n"
73
+ abort 'Passwords do not match' unless password == confirm
74
+
75
+ # Modify the user with the new password
76
+ begin
77
+ client.user_modify(client.config.username, password: password)
78
+ rescue StandardError => ex
79
+ abort "Failed to modify user: #{ex}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,98 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'securerandom'
16
+ require 'oj'
17
+ require 'yaml'
18
+ require 'logger'
19
+
20
+ require 'cyclid/config'
21
+ require 'cyclid/auth_methods'
22
+
23
+ require 'cyclid/client/api'
24
+ require 'cyclid/client/user'
25
+ require 'cyclid/client/organization'
26
+ require 'cyclid/client/job'
27
+ require 'cyclid/client/stage'
28
+ require 'cyclid/client/auth'
29
+ require 'cyclid/client/health'
30
+
31
+ module Cyclid
32
+ # Cyclid client methods
33
+ module Client
34
+ # Tilapia is the standard Cyclid Ruby client. It provides an inteligent
35
+ # programmable API on top of the standard Cyclid REST API, complete with
36
+ # automatic signing of HTTP requests and HTTP error handling.
37
+ #
38
+ # The client provides interfaces for managing Users, Organizations, Stages
39
+ # & Jobs. Refer to the documentation for those modules for more information.
40
+ #
41
+ # In case you're wondering, this class required a name: it couldn't be
42
+ # 'Cyclid' and it couldn't be 'Client'. Tilapia are a common type of
43
+ # Cichlid...
44
+ class Tilapia
45
+ attr_reader :config, :logger
46
+ # @!attribute [r] config
47
+ # @return [Config] Client configuration object
48
+ # @!attribute [r] logger
49
+ # @return [Logger] Client logger object
50
+
51
+ # @param [Hash] options
52
+ # @option options [String] :config_path Fully qualified path to the configuration file
53
+ # @option options [FixNum] :log_level Logger output level
54
+ def initialize(options)
55
+ @config = Config.new(options)
56
+
57
+ # Create a logger
58
+ log_level = options[:log_level] || Logger::FATAL
59
+ @logger = Logger.new(STDERR)
60
+ @logger.level = log_level
61
+
62
+ # Select the API methods to use
63
+ @api = case @config.auth
64
+ when AuthMethods::AUTH_NONE
65
+ Api::None.new(@config, @logger)
66
+ when AuthMethods::AUTH_HMAC
67
+ Api::Hmac.new(@config, @logger)
68
+ when AuthMethods::AUTH_BASIC
69
+ Api::Basic.new(@config, @logger)
70
+ when AuthMethods::AUTH_TOKEN
71
+ Api::Token.new(@config, @logger)
72
+ end
73
+ end
74
+
75
+ include User
76
+ include Organization
77
+ include Job
78
+ include Stage
79
+ include Auth
80
+ include Health
81
+
82
+ private
83
+
84
+ # Build a URI for the configured server & required resource
85
+ def server_uri(path)
86
+ URI::HTTP.build(host: @config.server,
87
+ port: @config.port,
88
+ path: path)
89
+ end
90
+
91
+ def method_missing(method, *args, &block)
92
+ @api.send(method, *args, &block)
93
+ end
94
+ end
95
+
96
+ include AuthMethods
97
+ end
98
+ end
@@ -0,0 +1,114 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'uri'
16
+ require 'net/http'
17
+
18
+ module Cyclid
19
+ # Cyclid client methods
20
+ module Client
21
+ # Client API request methods
22
+ module Api
23
+ # Base class for API request implementations
24
+ class Base
25
+ def initialize(config, logger)
26
+ @config = config
27
+ @logger = logger
28
+ end
29
+
30
+ # Add authentication details & perform a GET request
31
+ def api_get(uri)
32
+ req = authenticate_request(Net::HTTP::Get.new(uri), uri)
33
+
34
+ api_request(uri, req)
35
+ end
36
+
37
+ # Add authentication details perform a POST request with a pre-formatted body
38
+ def api_raw_post(uri, data, content_type)
39
+ unsigned = Net::HTTP::Post.new(uri)
40
+ unsigned.content_type = content_type
41
+ unsigned.body = data
42
+
43
+ req = authenticate_request(unsigned, uri)
44
+
45
+ api_request(uri, req)
46
+ end
47
+
48
+ # Add authentication details perform a POST request with a JSON body
49
+ def api_json_post(uri, data)
50
+ json = Oj.dump(data)
51
+ api_raw_post(uri, json, 'application/json')
52
+ end
53
+
54
+ # Add authentication details perform a POST request with a YAML body
55
+ def api_yaml_post(uri, data)
56
+ yaml = YAML.dump(data)
57
+ api_raw_post(uri, yaml, 'application/x-yaml')
58
+ end
59
+
60
+ # Add authentication details perform a PUT request with a JSON body
61
+ def api_json_put(uri, data)
62
+ unsigned = Net::HTTP::Put.new(uri)
63
+ unsigned.content_type = 'application/json'
64
+ unsigned.body = Oj.dump(data)
65
+
66
+ req = authenticate_request(unsigned, uri)
67
+
68
+ api_request(uri, req)
69
+ end
70
+
71
+ # Add authentication details perform a DELETE request
72
+ def api_delete(uri)
73
+ req = authenticate_request(Net::HTTP::Delete.new(uri), uri)
74
+
75
+ api_request(uri, req)
76
+ end
77
+
78
+ # Perform an API HTTP request & return the parsed response body
79
+ def api_request(uri, req)
80
+ http = Net::HTTP.new(uri.hostname, uri.port)
81
+ res = http.request(req)
82
+
83
+ parse_response(res)
84
+ end
85
+
86
+ # Parse, validate & handle response data
87
+ def parse_response(res)
88
+ response_data = Oj.load(res.body)
89
+
90
+ # Return immediately if the response was an HTTP 200 OK
91
+ return response_data if res.code == '200'
92
+
93
+ @logger.info "server request failed with error ##{res.code}: \
94
+ #{response_data['description']}"
95
+ raise response_data['description']
96
+ rescue Oj::ParseError => ex
97
+ @logger.debug "body: #{res.body}\n#{ex}"
98
+ raise 'failed to decode server response body'
99
+ end
100
+
101
+ # The API does not support non-authenticated requests, so this method
102
+ # must be implemented.
103
+ def authenticate_request(_request, _uri)
104
+ raise NotImplementedError
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ require 'cyclid/client/api/none'
112
+ require 'cyclid/client/api/hmac'
113
+ require 'cyclid/client/api/basic'
114
+ require 'cyclid/client/api/token'
@@ -0,0 +1,30 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Cyclid
16
+ # Cyclid client methods
17
+ module Client
18
+ # Client API HTTP methods
19
+ module Api
20
+ # HTTP Basic auth HTTP methods
21
+ class Basic < Base
22
+ # Add the username & password to the request
23
+ def authenticate_request(request, _uri)
24
+ request.basic_auth(@config.username, @config.password)
25
+ return request
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'cyclid/hmac'
16
+
17
+ module Cyclid
18
+ # Cyclid client methods
19
+ module Client
20
+ # Client API HTTP methods
21
+ module Api
22
+ # HMAC signed HTTP methods
23
+ class Hmac < Base
24
+ # Sign the request with HMAC
25
+ def authenticate_request(request, uri)
26
+ algorithm = 'sha256'
27
+ signer = Cyclid::HMAC::Signer.new(algorithm)
28
+
29
+ method = if request.is_a? Net::HTTP::Get
30
+ 'GET'
31
+ elsif request.is_a? Net::HTTP::Post
32
+ 'POST'
33
+ elsif request.is_a? Net::HTTP::Put
34
+ 'PUT'
35
+ elsif request.is_a? Net::HTTP::Delete
36
+ 'DELETE'
37
+ else
38
+ raise "invalid request method #{request.inspect}"
39
+ end
40
+
41
+ nonce = SecureRandom.hex
42
+ headers = signer.sign_request(uri.path,
43
+ @config.secret,
44
+ auth_header_format: '%{auth_scheme} %{username}:%{signature}', # rubocop:disable Metrics/LineLength
45
+ username: @config.username,
46
+ nonce: nonce,
47
+ method: method)
48
+
49
+ headers[0].each do |k, v|
50
+ request[k] = v
51
+ end
52
+ request['X-HMAC-Algorithm'] = algorithm
53
+
54
+ return request
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,29 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Cyclid
16
+ # Cyclid client methods
17
+ module Client
18
+ # Client API HTTP methods
19
+ module Api
20
+ # Only works for non-authenticated request (E.g. healthchecks)
21
+ class None < Base
22
+ # Do nothing
23
+ def authenticate_request(request, _uri)
24
+ request
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright 2016 Liqwyd Ltd.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Cyclid
16
+ # Cyclid client methods
17
+ module Client
18
+ # Client API HTTP methods
19
+ module Api
20
+ # JWT token based HTTP methods
21
+ class Token < Base
22
+ # Add the token to the request
23
+ def authenticate_request(request, _uri)
24
+ request.add_field('Authorization', "Token #{@config.username}:#{@config.token}")
25
+ return request
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end