conify 0.0.1
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 +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +9 -0
- data/Rakefile +6 -0
- data/bin/conify +30 -0
- data/config.ru +7 -0
- data/conify.gemspec +27 -0
- data/lib/.DS_Store +0 -0
- data/lib/conify.rb +2 -0
- data/lib/conify/api.rb +4 -0
- data/lib/conify/api/abstract_api.rb +78 -0
- data/lib/conify/api/addons.rb +19 -0
- data/lib/conify/api/users.rb +18 -0
- data/lib/conify/cli.rb +29 -0
- data/lib/conify/command.rb +286 -0
- data/lib/conify/command/abstract_command.rb +15 -0
- data/lib/conify/command/global.rb +109 -0
- data/lib/conify/helpers.rb +223 -0
- data/lib/conify/http.rb +56 -0
- data/lib/conify/manifest.rb +58 -0
- data/lib/conify/okjson.rb +606 -0
- data/lib/conify/sso.rb +89 -0
- data/lib/conify/test.rb +52 -0
- data/lib/conify/test/all_test.rb +23 -0
- data/lib/conify/test/api_test.rb +46 -0
- data/lib/conify/test/deprovision_test.rb +30 -0
- data/lib/conify/test/manifest_test.rb +88 -0
- data/lib/conify/test/plan_change_test.rb +33 -0
- data/lib/conify/test/provision_response_test.rb +89 -0
- data/lib/conify/test/provision_test.rb +52 -0
- data/lib/conify/test/sso_test.rb +58 -0
- data/lib/conify/version.rb +3 -0
- metadata +165 -0
data/lib/conify/sso.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'restclient'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Conify
|
5
|
+
class Sso
|
6
|
+
attr_accessor :external_uuid, :url, :proxy_port, :timestamp, :token
|
7
|
+
|
8
|
+
def initialize(data)
|
9
|
+
@external_uuid = data[:external_uuid]
|
10
|
+
@external_username = data[:external_username]
|
11
|
+
@salt = data['api']['sso_salt']
|
12
|
+
env = data.fetch('env', 'test')
|
13
|
+
|
14
|
+
if @url = data['api'][env]['sso_url']
|
15
|
+
@use_post = true
|
16
|
+
@proxy_port = find_available_port
|
17
|
+
else
|
18
|
+
@url = data['api'][env].chomp('/')
|
19
|
+
end
|
20
|
+
|
21
|
+
@timestamp = Time.now.to_i
|
22
|
+
@token = make_token(@timestamp)
|
23
|
+
end
|
24
|
+
|
25
|
+
def path
|
26
|
+
self.POST? ? URI.parse(url).path : "/conflux/resources/#{external_uuid}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def POST?
|
30
|
+
@use_post
|
31
|
+
end
|
32
|
+
|
33
|
+
def sso_url
|
34
|
+
self.POST? ? "http://localhost:#{@proxy_port}/" : full_url
|
35
|
+
end
|
36
|
+
|
37
|
+
def full_url
|
38
|
+
[ url, path, querystring ].join
|
39
|
+
end
|
40
|
+
alias get_url full_url
|
41
|
+
|
42
|
+
def post_url
|
43
|
+
url
|
44
|
+
end
|
45
|
+
|
46
|
+
def timestamp=(other)
|
47
|
+
@timestamp = other
|
48
|
+
@token = make_token(@timestamp)
|
49
|
+
end
|
50
|
+
|
51
|
+
def make_token(t)
|
52
|
+
Digest::SHA1.hexdigest([external_uuid, @salt, t].join(':'))
|
53
|
+
end
|
54
|
+
|
55
|
+
def querystring
|
56
|
+
return '' unless @salt
|
57
|
+
'?' + query_data
|
58
|
+
end
|
59
|
+
|
60
|
+
def query_data
|
61
|
+
query_params.map{|p| p.join('=')}.join('&')
|
62
|
+
end
|
63
|
+
|
64
|
+
def query_params
|
65
|
+
{
|
66
|
+
'id' => external_uuid,
|
67
|
+
'token' => @token,
|
68
|
+
'timestamp' => @timestamp.to_s,
|
69
|
+
'email' => @external_username
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def message
|
74
|
+
if self.POST?
|
75
|
+
"POSTing #{query_data} to #{post_url} via proxy on port #{@proxy_port}"
|
76
|
+
else
|
77
|
+
"Opening #{full_url}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_available_port
|
82
|
+
server = TCPServer.new('127.0.0.1', 0)
|
83
|
+
server.addr[1]
|
84
|
+
ensure
|
85
|
+
server.close if server
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
data/lib/conify/test.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'conify/helpers'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Conify
|
5
|
+
class Test
|
6
|
+
attr_accessor :data
|
7
|
+
include Conify::Helpers
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
def env
|
14
|
+
@data.fetch('env', 'test')
|
15
|
+
end
|
16
|
+
|
17
|
+
def test(msg, &block)
|
18
|
+
raise "Failed: #{msg}" unless block.call
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(klass, data)
|
22
|
+
test_name = klass.to_s.gsub('Conify::', '').split(/(?=[A-Z])/).join(' ')
|
23
|
+
|
24
|
+
begin
|
25
|
+
klass.new(data).call
|
26
|
+
rescue Exception => e
|
27
|
+
error "#{test_name} #{e.message}"
|
28
|
+
end
|
29
|
+
|
30
|
+
if klass.const_defined?('OUTPUT_COMPLETION') && klass.const_get('OUTPUT_COMPLETION')
|
31
|
+
display "#{test_name}: Looks good..."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def url
|
36
|
+
if data['api'][env].is_a? Hash
|
37
|
+
base = data['api'][env]['base_url']
|
38
|
+
uri = URI.parse(base)
|
39
|
+
uri.query = nil
|
40
|
+
uri.path = ''
|
41
|
+
uri.to_s
|
42
|
+
else
|
43
|
+
data['api'][env].chomp('/')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def api_requires?(feature)
|
48
|
+
data['api'].fetch('requires', []).include?(feature)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'conify/test'
|
2
|
+
require 'conify/test/manifest_test'
|
3
|
+
require 'conify/test/provision_test'
|
4
|
+
require 'conify/test/plan_change_test'
|
5
|
+
require 'conify/test/deprovision_test'
|
6
|
+
require 'conify/test/sso_test'
|
7
|
+
|
8
|
+
class Conify::AllTest < Conify::Test
|
9
|
+
|
10
|
+
def call
|
11
|
+
run(Conify::ManifestTest, data)
|
12
|
+
run(Conify::ProvisionTest, data)
|
13
|
+
|
14
|
+
# data[:provision_response] has already been set from the above test,
|
15
|
+
# so add 'external_uuid' returned from inside this hash to the data object.
|
16
|
+
data[:external_uuid] = data[:provision_response]['id']
|
17
|
+
|
18
|
+
run(Conify::PlanChangeTest, data)
|
19
|
+
run(Conify::SsoTest, data)
|
20
|
+
run(Conify::DeprovisionTest, data)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'conify/test'
|
2
|
+
require 'conify/http'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
class Conify::ApiTest < Conify::Test
|
6
|
+
include Conify::HTTPForTests
|
7
|
+
|
8
|
+
def base_path
|
9
|
+
if data['api'][env].is_a?(Hash)
|
10
|
+
URI.parse(data['api'][env]['base_url']).path
|
11
|
+
else
|
12
|
+
'/conflux/resources'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def conflux_id
|
17
|
+
"app#{rand(10000)}@conify.goconflux.com"
|
18
|
+
end
|
19
|
+
|
20
|
+
def credentials
|
21
|
+
[ data['id'], data['api']['password'] ]
|
22
|
+
end
|
23
|
+
|
24
|
+
def invalid_creds
|
25
|
+
['wrong', 'secret']
|
26
|
+
end
|
27
|
+
|
28
|
+
def callback
|
29
|
+
'http://localhost:7779/callback/999'
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_provision_payload
|
33
|
+
payload = {
|
34
|
+
conflux_id: conflux_id,
|
35
|
+
plan: 'test',
|
36
|
+
callback_url: callback,
|
37
|
+
logplex_token: nil,
|
38
|
+
uuid: SecureRandom.uuid
|
39
|
+
}
|
40
|
+
|
41
|
+
payload[:log_drain_token] = SecureRandom.hex if api_requires?('syslog_drain')
|
42
|
+
|
43
|
+
payload
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'conify/test/api_test'
|
2
|
+
|
3
|
+
class Conify::DeprovisionTest < Conify::ApiTest
|
4
|
+
|
5
|
+
OUTPUT_COMPLETION = true
|
6
|
+
|
7
|
+
def call
|
8
|
+
external_uuid = data[:external_uuid]
|
9
|
+
raise ArgumentError, 'Deprovision Test: No external_uuid specified' if external_uuid.nil?
|
10
|
+
path = "#{base_path}/#{external_uuid.to_s}"
|
11
|
+
|
12
|
+
test 'response' do
|
13
|
+
code, _ = delete(credentials, path, nil)
|
14
|
+
if code == 200
|
15
|
+
true
|
16
|
+
elsif code == -1
|
17
|
+
error "Deprovision Test: unable to connect to #{url}"
|
18
|
+
else
|
19
|
+
error "Deprovision Test: expected 200, got #{code}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
test 'authentication' do
|
24
|
+
code, _ = delete(invalid_creds, path, nil)
|
25
|
+
error "Deprovision Test: expected 401, got #{code}" if code != 401
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'conify/test'
|
2
|
+
|
3
|
+
class Conify::ManifestTest < Conify::Test
|
4
|
+
|
5
|
+
OUTPUT_COMPLETION = true
|
6
|
+
|
7
|
+
def call
|
8
|
+
test 'id key exists' do
|
9
|
+
data.has_key?('id')
|
10
|
+
end
|
11
|
+
|
12
|
+
test 'id is a string' do
|
13
|
+
data['id'].is_a?(String)
|
14
|
+
end
|
15
|
+
|
16
|
+
test 'id is not blank' do
|
17
|
+
!data['id'].empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'api key exists' do
|
21
|
+
data.has_key?('api')
|
22
|
+
end
|
23
|
+
|
24
|
+
test 'api is a hash' do
|
25
|
+
data['api'].is_a?(Hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
test 'api contains password' do
|
29
|
+
data['api'].has_key?('password') && data['api']['password'] != ''
|
30
|
+
end
|
31
|
+
|
32
|
+
test 'api contains sso_salt' do
|
33
|
+
data['api'].has_key?('sso_salt') && data['api']['sso_salt'] != ''
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'api contains test url' do
|
37
|
+
data['api'].has_key?('test')
|
38
|
+
end
|
39
|
+
|
40
|
+
test 'api contains production url' do
|
41
|
+
data['api'].has_key?('production')
|
42
|
+
end
|
43
|
+
|
44
|
+
if data['api']['production'].is_a?(Hash)
|
45
|
+
test 'production url uses SSL' do
|
46
|
+
data['api']['production']['base_url'] =~ /^https:/
|
47
|
+
end
|
48
|
+
|
49
|
+
test 'sso url uses SSL' do
|
50
|
+
data['api']['production']['sso_url'] =~ /^https:/
|
51
|
+
end
|
52
|
+
else
|
53
|
+
test 'production url uses SSL' do
|
54
|
+
data['api']['production'] =~ /^https:/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
if data['api'].has_key?('config_vars')
|
59
|
+
test 'contains config_vars array' do
|
60
|
+
data['api']['config_vars'].is_a?(Array)
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'all config vars are uppercase strings' do
|
64
|
+
data['api']['config_vars'].each do |k, v|
|
65
|
+
if k =~ /^[A-Z][0-9A-Z_]+$/
|
66
|
+
true
|
67
|
+
else
|
68
|
+
error "#{k.inspect} is not a valid ENV key"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
test 'all config vars are prefixed with the addon id' do
|
74
|
+
data['api']['config_vars'].each do |k|
|
75
|
+
prefix = data['id'].upcase.gsub('-', '_')
|
76
|
+
|
77
|
+
if k =~ /^#{prefix}_/
|
78
|
+
true
|
79
|
+
else
|
80
|
+
error "#{k} is not a valid ENV key - must be prefixed with #{prefix}_"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'conify/test/api_test'
|
2
|
+
|
3
|
+
class Conify::PlanChangeTest < Conify::ApiTest
|
4
|
+
|
5
|
+
OUTPUT_COMPLETION = true
|
6
|
+
|
7
|
+
def call
|
8
|
+
external_uuid = data[:external_uuid]
|
9
|
+
raise ArgumentError, 'Plan Change Test: No external_uuid specified' if external_uuid.nil?
|
10
|
+
|
11
|
+
path = "#{base_path}/#{external_uuid.to_s}"
|
12
|
+
payload = { plan: 'new_plan', conflux_id: data[:external_username] }
|
13
|
+
|
14
|
+
test 'response' do
|
15
|
+
code, _ = put(credentials, path, payload)
|
16
|
+
|
17
|
+
if code == 200
|
18
|
+
true
|
19
|
+
elsif code == -1
|
20
|
+
error "Plan Change Test: unable to connect to #{url}"
|
21
|
+
else
|
22
|
+
error "Plan Change Test: expected 200, got #{code}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
test 'authentication' do
|
27
|
+
code, _ = put(invalid_creds, path, payload)
|
28
|
+
error "Plan Change Test: expected 401, got #{code}" if code != 401
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'conify/test'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Conify::ProvisionResponseTest < Conify::Test
|
5
|
+
|
6
|
+
def call
|
7
|
+
response = data[:provision_response]
|
8
|
+
|
9
|
+
test 'contains an id' do
|
10
|
+
response.is_a?(Hash) && response['id']
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'id does not contain conflux_id' do
|
14
|
+
if response['id'].to_s.include? data[:external_username].scan(/app(\d+)@/).flatten.first
|
15
|
+
error 'id cannot include conflux_id'
|
16
|
+
else
|
17
|
+
true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if response.has_key?('config')
|
22
|
+
test 'is a hash' do
|
23
|
+
response['config'].is_a?(Hash)
|
24
|
+
end
|
25
|
+
|
26
|
+
test 'all config keys were previously defined in the manifest' do
|
27
|
+
response['config'].keys.each do |key|
|
28
|
+
error "#{key} is not in the manifest" unless data['api']['config_vars'].include?(key)
|
29
|
+
end
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
test 'all keys in the manifest are present' do
|
34
|
+
difference = data['api']['config_vars'] - response['config'].keys
|
35
|
+
unless difference.empty?
|
36
|
+
verb = (difference.size == 1) ? 'is' : 'are'
|
37
|
+
error "Config(s) #{difference.join(', ')} #{verb} missing from the provision response"
|
38
|
+
end
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
test 'all config values are strings' do
|
43
|
+
response['config'].each do |k, v|
|
44
|
+
if v.is_a?(String)
|
45
|
+
true
|
46
|
+
else
|
47
|
+
error "the key #{k} doesn't contain a string (#{v.inspect})"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
test 'URL configs vars' do
|
53
|
+
response['config'].each do |key, value|
|
54
|
+
next unless key =~ /_URL$/
|
55
|
+
begin
|
56
|
+
uri = URI.parse(value)
|
57
|
+
error "#{value} is not a valid URI - missing host" unless uri.host
|
58
|
+
error "#{value} is not a valid URI - missing scheme" unless uri.scheme
|
59
|
+
error "#{value} is not a valid URI - pointing to localhost" if env == 'production' && uri.host == 'localhost'
|
60
|
+
rescue URI::Error
|
61
|
+
error "#{value} is not a valid URI"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
test 'log_drain_url is returned if required' do
|
67
|
+
if !api_requires?('syslog_drain')
|
68
|
+
true
|
69
|
+
else
|
70
|
+
drain_url = response['log_drain_url']
|
71
|
+
|
72
|
+
if !drain_url || drain_url.empty?
|
73
|
+
error 'must return a log_drain_url'
|
74
|
+
else
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
unless drain_url =~ /\A(https|syslog):\/\/[\S]+\Z/
|
79
|
+
error 'must return a syslog_drain_url like syslog://log.example.com:9999'
|
80
|
+
else
|
81
|
+
true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|