conify 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|