bixby-common 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +40 -0
- data/Gemfile.lock +147 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/bixby-common.gemspec +135 -0
- data/lib/bixby-common.rb +1 -0
- data/lib/bixby_common/api/json_request.rb +36 -0
- data/lib/bixby_common/api/json_response.rb +83 -0
- data/lib/bixby_common/bixby.rb +23 -0
- data/lib/bixby_common/command_response.rb +80 -0
- data/lib/bixby_common/command_spec.rb +138 -0
- data/lib/bixby_common/exception/bundle_not_found.rb +5 -0
- data/lib/bixby_common/exception/command_exception.rb +13 -0
- data/lib/bixby_common/exception/command_not_found.rb +5 -0
- data/lib/bixby_common/exception/encryption_error.rb +5 -0
- data/lib/bixby_common/util/crypto_util.rb +130 -0
- data/lib/bixby_common/util/debug.rb +19 -0
- data/lib/bixby_common/util/hashify.rb +16 -0
- data/lib/bixby_common/util/http_client.rb +64 -0
- data/lib/bixby_common/util/jsonify.rb +27 -0
- data/lib/bixby_common/util/log.rb +50 -0
- data/lib/bixby_common.rb +24 -0
- data/test/bixby_common_test.rb +18 -0
- data/test/command_response_test.rb +48 -0
- data/test/command_spec_test.rb +98 -0
- data/test/helper.rb +34 -0
- data/test/support/repo/vendor/test_bundle/bin/cat +2 -0
- data/test/support/repo/vendor/test_bundle/bin/cat.json +3 -0
- data/test/support/repo/vendor/test_bundle/bin/echo +2 -0
- data/test/support/repo/vendor/test_bundle/digest +20 -0
- data/test/support/repo/vendor/test_bundle/manifest.json +0 -0
- data/test/util/http_client_test.rb +58 -0
- data/test/util/jsonify_test.rb +36 -0
- data/test/util/log_test.rb +26 -0
- metadata +450 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
|
2
|
+
require 'digest'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Bixby
|
6
|
+
|
7
|
+
# Describes a Command execution request for the Agent
|
8
|
+
class CommandSpec
|
9
|
+
|
10
|
+
include Jsonify
|
11
|
+
include Hashify
|
12
|
+
|
13
|
+
attr_accessor :repo, :digest, :bundle, :command, :args, :stdin, :env
|
14
|
+
|
15
|
+
# Create new CommandSpec
|
16
|
+
#
|
17
|
+
# @params [Hash] params Hash of attributes to initialize with
|
18
|
+
def initialize(params = nil)
|
19
|
+
return if params.nil? or params.empty?
|
20
|
+
params.each{ |k,v| self.send("#{k}=", v) if self.respond_to? "#{k}=" }
|
21
|
+
|
22
|
+
digest = load_digest()
|
23
|
+
@digest = digest["digest"] if digest
|
24
|
+
end
|
25
|
+
|
26
|
+
# Validate the existence of this Command on the local system
|
27
|
+
# and compare digest to local version
|
28
|
+
#
|
29
|
+
# @param [String] expected_digest
|
30
|
+
# @return [Boolean] returns true if available, else raises error
|
31
|
+
# @raise [BundleNotFound] If bundle doesn't exist or digest does not match
|
32
|
+
# @raise [CommandNotFound] If command doesn't exist
|
33
|
+
def validate(expected_digest)
|
34
|
+
if not bundle_exists? then
|
35
|
+
raise BundleNotFound.new("repo = #{@repo}; bundle = #{@bundle}")
|
36
|
+
end
|
37
|
+
|
38
|
+
if not command_exists? then
|
39
|
+
raise CommandNotFound.new("repo = #{@repo}; bundle = #{@bundle}; command = #{@command}")
|
40
|
+
end
|
41
|
+
if self.digest != expected_digest then
|
42
|
+
raise BundleNotFound, "digest does not match ('#{self.digest}' != '#{expected_digest}'", caller
|
43
|
+
end
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
# resolve the given bundle
|
48
|
+
def bundle_dir
|
49
|
+
File.join(Bixby.repo_path, self.relative_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return the relative path to the bundle (inside the repository)
|
53
|
+
#
|
54
|
+
# e.g., if Bixby.repo_path = /opt/bixby/repo then a relative path would
|
55
|
+
# look like:
|
56
|
+
#
|
57
|
+
# vendor/system/monitoring
|
58
|
+
# or
|
59
|
+
# megacorp/sysops/scripts
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def relative_path
|
63
|
+
File.join(@repo, @bundle)
|
64
|
+
end
|
65
|
+
|
66
|
+
def bundle_exists?
|
67
|
+
File.exists? self.bundle_dir
|
68
|
+
end
|
69
|
+
|
70
|
+
def command_file
|
71
|
+
File.join(self.bundle_dir, "bin", @command)
|
72
|
+
end
|
73
|
+
|
74
|
+
def command_exists?
|
75
|
+
File.exists? self.command_file
|
76
|
+
end
|
77
|
+
|
78
|
+
def config_file
|
79
|
+
command_file + ".json"
|
80
|
+
end
|
81
|
+
|
82
|
+
def load_config
|
83
|
+
if File.exists? config_file then
|
84
|
+
MultiJson.load(File.read(config_file))
|
85
|
+
else
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def digest_file
|
91
|
+
File.join(self.bundle_dir, "digest")
|
92
|
+
end
|
93
|
+
|
94
|
+
def load_digest
|
95
|
+
begin
|
96
|
+
return MultiJson.load(File.read(digest_file))
|
97
|
+
rescue => ex
|
98
|
+
end
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def update_digest
|
103
|
+
|
104
|
+
path = self.bundle_dir
|
105
|
+
sha = Digest::SHA2.new
|
106
|
+
bundle_sha = Digest::SHA2.new
|
107
|
+
|
108
|
+
digests = []
|
109
|
+
Dir.glob("#{path}/**/*").sort.each do |f|
|
110
|
+
next if File.directory? f or File.basename(f) == "digest"
|
111
|
+
bundle_sha.file(f)
|
112
|
+
sha.reset()
|
113
|
+
digests << { :file => f.gsub(/#{path}\//, ''), :digest => sha.file(f).hexdigest() }
|
114
|
+
end
|
115
|
+
|
116
|
+
@digest = { :digest => bundle_sha.hexdigest(), :files => digests }
|
117
|
+
File.open(path+"/digest", 'w'){ |f| f.write(MultiJson.dump(@digest, :pretty => true) + "\n") }
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
# Convert object to String, useful for debugging
|
122
|
+
#
|
123
|
+
# @return [String]
|
124
|
+
def to_s # :nocov:
|
125
|
+
s = []
|
126
|
+
s << "CommandSpec:#{self.object_id}"
|
127
|
+
s << " digest: #{self.digest}"
|
128
|
+
s << " repo: #{self.repo}"
|
129
|
+
s << " bundle: #{self.bundle}"
|
130
|
+
s << " command: #{self.command}"
|
131
|
+
s << " args: #{self.args}"
|
132
|
+
s << " env: " + MultiJson.dump(self.env)
|
133
|
+
s << " stdin: " + Debug.pretty_str(stdin)
|
134
|
+
s.join("\n")
|
135
|
+
end # :nocov:
|
136
|
+
|
137
|
+
end # CommandSpec
|
138
|
+
end # Bixby
|
@@ -0,0 +1,130 @@
|
|
1
|
+
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
module Bixby
|
7
|
+
module CryptoUtil
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Encrypt the given payload for over-the-wire transmission
|
12
|
+
#
|
13
|
+
# @param [Object] data payload, usually a JSON-encoded String
|
14
|
+
# @param [String] uuid UUID of the sender
|
15
|
+
# @param [OpenSSL::PKey::RSA] key_pem Public key of the receiver
|
16
|
+
# @param [OpenSSL::PKey::RSA] iv_pem Private key of the sender
|
17
|
+
def encrypt(data, uuid, key_pem, iv_pem)
|
18
|
+
c = new_cipher()
|
19
|
+
c.encrypt
|
20
|
+
key = c.random_key
|
21
|
+
iv = c.random_iv
|
22
|
+
|
23
|
+
data = Time.new.to_i.to_s + "\n" + data # prepend timestamp
|
24
|
+
encrypted = c.update(data) + c.final
|
25
|
+
|
26
|
+
out = []
|
27
|
+
out << uuid
|
28
|
+
out << create_hmac(key, iv, encrypted)
|
29
|
+
out << w( key_pem.public_encrypt(key) )
|
30
|
+
out << w( iv_pem.private_encrypt(iv) )
|
31
|
+
out << e64(encrypted)
|
32
|
+
|
33
|
+
return out.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Decrypt the given payload from over-the-wire transmission
|
37
|
+
#
|
38
|
+
# @param [Object] data encrypted payload, usually a JSON-encoded String
|
39
|
+
# @param [OpenSSL::PKey::RSA] key_pem Private key of the receiver
|
40
|
+
# @param [OpenSSL::PKey::RSA] iv_pem Public key of the sender
|
41
|
+
def decrypt(data, key_pem, iv_pem)
|
42
|
+
data = StringIO.new(data, 'rb') if not data.kind_of? StringIO
|
43
|
+
hmac = data.readline.strip
|
44
|
+
key = key_pem.private_decrypt(read_next(data))
|
45
|
+
iv = iv_pem.public_decrypt(read_next(data))
|
46
|
+
|
47
|
+
c = new_cipher()
|
48
|
+
c.decrypt
|
49
|
+
c.key = key
|
50
|
+
c.iv = iv
|
51
|
+
|
52
|
+
payload = d64(data.read)
|
53
|
+
|
54
|
+
# very hmac of encrypted payload
|
55
|
+
if not verify_hmac(hmac, key, iv, payload) then
|
56
|
+
raise Bixby::EncryptionError, "hmac verification failed", caller
|
57
|
+
end
|
58
|
+
|
59
|
+
data = StringIO.new(c.update(payload) + c.final)
|
60
|
+
|
61
|
+
ts = data.readline.strip
|
62
|
+
if (Time.new.to_i - ts.to_i) > 60 then
|
63
|
+
raise Bixby::EncryptionError, "payload verification failed", caller
|
64
|
+
end
|
65
|
+
|
66
|
+
return data.read
|
67
|
+
end
|
68
|
+
|
69
|
+
def generate_access_key
|
70
|
+
Digest.hexencode(Digest::MD5.new.digest(OpenSSL::Random.random_bytes(512)))
|
71
|
+
end
|
72
|
+
|
73
|
+
def generate_secret_key
|
74
|
+
Digest.hexencode(Digest::SHA2.new(512).digest(OpenSSL::Random.random_bytes(512)))
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Compute an HMAC using SHA2-256
|
81
|
+
#
|
82
|
+
# @param [String] key
|
83
|
+
# @param [String] iv
|
84
|
+
# @param [String] payload encrypted payload
|
85
|
+
#
|
86
|
+
# @return [String] digest in hexadecimal format
|
87
|
+
def create_hmac(key, iv, payload)
|
88
|
+
d = Digest::SHA2.new(256)
|
89
|
+
d << key << iv << payload
|
90
|
+
return d.hexdigest()
|
91
|
+
end
|
92
|
+
|
93
|
+
# Verify the given HMAC of the incoming message
|
94
|
+
#
|
95
|
+
# @param [String] hmac
|
96
|
+
# @param [String] key
|
97
|
+
# @param [String] iv
|
98
|
+
# @param [String] payload encrypted payload
|
99
|
+
#
|
100
|
+
# @return [Boolean] true if hmac matches
|
101
|
+
def verify_hmac(hmac, key, iv, payload)
|
102
|
+
create_hmac(key, iv, payload) == hmac
|
103
|
+
end
|
104
|
+
|
105
|
+
def new_cipher
|
106
|
+
# TODO make this configurable? perhaps use CTR when available
|
107
|
+
# we can store a CTR support flag on the master
|
108
|
+
OpenSSL::Cipher.new("AES-256-CBC")
|
109
|
+
end
|
110
|
+
|
111
|
+
def w(s)
|
112
|
+
e64(s).gsub(/\n/, "\\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_next(data)
|
116
|
+
d64(data.readline.gsub(/\\n/, "\n"))
|
117
|
+
end
|
118
|
+
|
119
|
+
def e64(s)
|
120
|
+
Base64.encode64(s)
|
121
|
+
end
|
122
|
+
|
123
|
+
def d64(s)
|
124
|
+
Base64.decode64(s)
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end # CryptoUtil
|
130
|
+
end # Bixby
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
module Bixby
|
3
|
+
module Debug
|
4
|
+
|
5
|
+
# Simple helper for use in to_s methods
|
6
|
+
def self.pretty_str(str) # :nocov:
|
7
|
+
if str.nil? then
|
8
|
+
"nil"
|
9
|
+
elsif str.empty? then
|
10
|
+
'""'
|
11
|
+
elsif str.include? "\n" then
|
12
|
+
"<<-EOF\n" + str + "\nEOF"
|
13
|
+
else
|
14
|
+
'"' + str + '"'
|
15
|
+
end
|
16
|
+
end # :nocov:
|
17
|
+
|
18
|
+
end # Debug
|
19
|
+
end # Bixby
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
module Bixby
|
3
|
+
|
4
|
+
# Adds to_hash method to an Object
|
5
|
+
module Hashify
|
6
|
+
|
7
|
+
# Creates a Hash representation of self
|
8
|
+
#
|
9
|
+
# @return [Hash]
|
10
|
+
def to_hash
|
11
|
+
self.instance_variables.inject({}) { |m,v| m[v[1,v.length].to_sym] = instance_variable_get(v); m }
|
12
|
+
end
|
13
|
+
|
14
|
+
end # Hashify
|
15
|
+
|
16
|
+
end # Bixby
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
require 'httpi'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
HTTPI.log = false
|
6
|
+
|
7
|
+
module Bixby
|
8
|
+
|
9
|
+
# Utilities for using HTTP Clients. Just a thin wrapper around httpi and JSON
|
10
|
+
# for common cases.
|
11
|
+
module HttpClient
|
12
|
+
|
13
|
+
# Execute an HTTP GET request to the given URL
|
14
|
+
#
|
15
|
+
# @param [String] url
|
16
|
+
# @return [String] Contents of the response's body
|
17
|
+
def http_get(url)
|
18
|
+
HTTPI.get(url).body
|
19
|
+
end
|
20
|
+
|
21
|
+
# Execute an HTTP GET request (see #http_get) and parse the JSON response
|
22
|
+
#
|
23
|
+
# @param [String] url
|
24
|
+
# @return [Object] Result of calling JSON.parse() on the response body
|
25
|
+
def http_get_json(url)
|
26
|
+
MultiJson.load(http_get(url))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Execute an HTTP POST request to the given URL
|
30
|
+
#
|
31
|
+
# @param [String] url
|
32
|
+
# @param [Hash] data Key/Value pairs to POST
|
33
|
+
# @return [String] Contents of the response's body
|
34
|
+
def http_post(url, data)
|
35
|
+
req = HTTPI::Request.new(:url => url, :body => data)
|
36
|
+
return HTTPI.post(req).body
|
37
|
+
end
|
38
|
+
|
39
|
+
# Execute an HTTP POST request (see #http_get) and parse the JSON response
|
40
|
+
#
|
41
|
+
# @param [String] url
|
42
|
+
# @param [Hash] data Key/Value pairs to POST
|
43
|
+
# @return [Object] Result of calling JSON.parse() on the response body
|
44
|
+
def http_post_json(url, data)
|
45
|
+
MultiJson.load(http_post(url, data))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Execute an HTTP post request and save the response body
|
49
|
+
#
|
50
|
+
# @param [String] url
|
51
|
+
# @param [Hash] data Key/Value pairs to POST
|
52
|
+
# @return [void]
|
53
|
+
def http_post_download(url, data, dest)
|
54
|
+
File.open(dest, "w") do |io|
|
55
|
+
req = HTTPI::Request.new(:url => url, :body => data)
|
56
|
+
req.on_body { |d| io << d; d.length }
|
57
|
+
HTTPI.post(req)
|
58
|
+
end
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
end # HttpClient
|
63
|
+
|
64
|
+
end # Bixby
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Bixby
|
5
|
+
module Jsonify
|
6
|
+
|
7
|
+
include Hashify
|
8
|
+
|
9
|
+
def to_json
|
10
|
+
MultiJson.dump(self.to_hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def from_json(json)
|
15
|
+
json = MultiJson.load(json) if json.kind_of? String
|
16
|
+
obj = self.allocate
|
17
|
+
json.each{ |k,v| obj.send("#{k}=".to_sym, v) }
|
18
|
+
obj
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(receiver)
|
23
|
+
receiver.extend(ClassMethods)
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Jsonify
|
27
|
+
end # Bixby
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
require "logging"
|
3
|
+
|
4
|
+
module Bixby
|
5
|
+
|
6
|
+
# A simple logging mixin
|
7
|
+
module Log
|
8
|
+
|
9
|
+
# Get a log instance for this class
|
10
|
+
#
|
11
|
+
# @return [Logger]
|
12
|
+
def log
|
13
|
+
@log ||= Logging.logger[self]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Create a method for each log level. Allows receiver to simply call
|
17
|
+
#
|
18
|
+
# warn "foo"
|
19
|
+
%w{debug warn info error fatal}.each do |level|
|
20
|
+
code = <<-EOF
|
21
|
+
def #{level}(data=nil, &block)
|
22
|
+
log.send(:#{level}, data, &block)
|
23
|
+
end
|
24
|
+
EOF
|
25
|
+
eval(code)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Setup logging
|
29
|
+
#
|
30
|
+
# @param [Symbol] level Log level to use (default = :warn)
|
31
|
+
# @param [String] pattern Log pattern
|
32
|
+
def self.setup_logger(level=nil, pattern=nil)
|
33
|
+
# set level: ENV flag overrides; default to warn
|
34
|
+
level = :debug if ENV["BIXBY_DEBUG"]
|
35
|
+
level ||= :warn
|
36
|
+
|
37
|
+
pattern ||= '%.1l, [%d] %5l -- %c: %m\n'
|
38
|
+
|
39
|
+
# TODO always use stdout for now
|
40
|
+
Logging.appenders.stdout(
|
41
|
+
:level => level,
|
42
|
+
:layout => Logging.layouts.pattern(:pattern => pattern)
|
43
|
+
)
|
44
|
+
Logging::Logger.root.add_appenders(Logging.appenders.stdout)
|
45
|
+
Logging::Logger.root.level = level
|
46
|
+
end
|
47
|
+
|
48
|
+
end # Log
|
49
|
+
|
50
|
+
end
|
data/lib/bixby_common.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require "bixby_common/bixby"
|
3
|
+
|
4
|
+
module Bixby
|
5
|
+
|
6
|
+
autoload :CommandResponse, "bixby_common/command_response"
|
7
|
+
autoload :CommandSpec, "bixby_common/command_spec"
|
8
|
+
|
9
|
+
autoload :JsonRequest, "bixby_common/api/json_request"
|
10
|
+
autoload :JsonResponse, "bixby_common/api/json_response"
|
11
|
+
|
12
|
+
autoload :BundleNotFound, "bixby_common/exception/bundle_not_found"
|
13
|
+
autoload :CommandNotFound, "bixby_common/exception/command_not_found"
|
14
|
+
autoload :CommandException, "bixby_common/exception/command_exception"
|
15
|
+
autoload :EncryptionError, "bixby_common/exception/encryption_error"
|
16
|
+
|
17
|
+
autoload :CryptoUtil, "bixby_common/util/crypto_util"
|
18
|
+
autoload :HttpClient, "bixby_common/util/http_client"
|
19
|
+
autoload :Jsonify, "bixby_common/util/jsonify"
|
20
|
+
autoload :Hashify, "bixby_common/util/hashify"
|
21
|
+
autoload :Log, "bixby_common/util/log"
|
22
|
+
autoload :Debug, "bixby_common/util/debug"
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module Bixby
|
4
|
+
module Test
|
5
|
+
|
6
|
+
class TestBixbyCommon < MiniTest::Unit::TestCase
|
7
|
+
|
8
|
+
def test_autoloading
|
9
|
+
assert_equal(JsonRequest, JsonRequest.new(nil, nil).class)
|
10
|
+
assert_equal(BundleNotFound, BundleNotFound.new.class)
|
11
|
+
assert_equal(CommandNotFound, CommandNotFound.new.class)
|
12
|
+
assert_equal(CommandSpec, CommandSpec.new.class)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end # Test
|
18
|
+
end # Bixby
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
module Bixby
|
5
|
+
module Test
|
6
|
+
|
7
|
+
class TestCommandResponse < MiniTest::Unit::TestCase
|
8
|
+
|
9
|
+
def test_from_json_response
|
10
|
+
res = JsonResponse.new("fail", "unknown")
|
11
|
+
assert res.fail?
|
12
|
+
refute res.success?
|
13
|
+
cr = CommandResponse.from_json_response(res)
|
14
|
+
assert_kind_of CommandResponse, cr
|
15
|
+
assert_equal 255, cr.status
|
16
|
+
assert_equal "unknown", cr.stderr
|
17
|
+
begin
|
18
|
+
cr.raise!
|
19
|
+
rescue CommandException => ex
|
20
|
+
assert_equal "unknown", ex.message
|
21
|
+
end
|
22
|
+
|
23
|
+
res = JsonResponse.new("success", nil, {:status => 0, :stdout => "foobar", :stderr => nil})
|
24
|
+
assert res.success?
|
25
|
+
refute res.fail?
|
26
|
+
cr = CommandResponse.from_json_response(res)
|
27
|
+
assert_kind_of CommandResponse, cr
|
28
|
+
assert_equal 0, cr.status
|
29
|
+
assert_equal "foobar", cr.stdout
|
30
|
+
assert_nil cr.stderr
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_status
|
34
|
+
cr = CommandResponse.new
|
35
|
+
cr.status = 0
|
36
|
+
assert cr.success?
|
37
|
+
refute cr.fail?
|
38
|
+
refute cr.error?
|
39
|
+
|
40
|
+
cr.status = "255"
|
41
|
+
refute cr.success?
|
42
|
+
assert cr.fail?
|
43
|
+
end
|
44
|
+
|
45
|
+
end # TestCommandResponse
|
46
|
+
|
47
|
+
end # Test
|
48
|
+
end # Bixby
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
|
5
|
+
module Bixby
|
6
|
+
module Test
|
7
|
+
|
8
|
+
class TestCommandSpec < MiniTest::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
ENV["BIXBY_HOME"] = File.join(File.expand_path(File.dirname(__FILE__)), "support")
|
12
|
+
h = { :repo => "vendor", :bundle => "test_bundle", 'command' => "echo", :foobar => "baz" }
|
13
|
+
@c = CommandSpec.new(h)
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
super
|
18
|
+
system("rm -rf /tmp/_test_bixby_home")
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def test_init_with_hash
|
23
|
+
assert(@c)
|
24
|
+
assert_equal("vendor", @c.repo)
|
25
|
+
assert_equal("test_bundle", @c.bundle)
|
26
|
+
assert_equal("echo", @c.command)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_to_hash
|
30
|
+
assert_equal("vendor", @c.to_hash[:repo])
|
31
|
+
assert_equal("test_bundle", @c.to_hash[:bundle])
|
32
|
+
assert_equal("echo", @c.to_hash[:command])
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_load_config
|
36
|
+
config = @c.load_config
|
37
|
+
assert config
|
38
|
+
assert_kind_of Hash, config
|
39
|
+
assert_empty config
|
40
|
+
|
41
|
+
@c.command = "cat"
|
42
|
+
config = @c.load_config
|
43
|
+
assert config
|
44
|
+
assert_equal "cat", config["name"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_validate_failures
|
48
|
+
assert_throws(BundleNotFound) do
|
49
|
+
CommandSpec.new(:repo => "vendor", :bundle => "foobar").validate(nil)
|
50
|
+
end
|
51
|
+
assert_throws(CommandNotFound) do
|
52
|
+
CommandSpec.new(:repo => "vendor", :bundle => "test_bundle", :command => "foobar").validate(nil)
|
53
|
+
end
|
54
|
+
assert_throws(BundleNotFound) do
|
55
|
+
@c.validate(nil)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_digest
|
60
|
+
expected_digest = "8980372485fc6bcd287e481ab1e15710e2b63c68db75085c2d24386ced272ca4"
|
61
|
+
assert_equal expected_digest, @c.digest
|
62
|
+
assert @c.validate(expected_digest)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_digest_no_err
|
66
|
+
c = CommandSpec.new({ :repo => "vendor", :bundle => "test_bundle", 'command' => "echofoo" })
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_exec_digest_changed_throws_error
|
70
|
+
assert_throws(BundleNotFound) do
|
71
|
+
@c.validate("alkjasdfasd")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_update_digest
|
76
|
+
expected = MultiJson.load(File.read(Bixby.repo_path + "/vendor/test_bundle/digest"))
|
77
|
+
|
78
|
+
t = "/tmp/_test_bixby_home"
|
79
|
+
d = "#{t}/repo/vendor/test_bundle/digest"
|
80
|
+
`mkdir -p #{t}`
|
81
|
+
`cp -a #{Bixby.repo_path}/ #{t}/`
|
82
|
+
`rm #{d}`
|
83
|
+
ENV["BIXBY_HOME"] = t
|
84
|
+
|
85
|
+
refute File.exist? d
|
86
|
+
@c.update_digest
|
87
|
+
assert File.exist? d
|
88
|
+
assert_equal MultiJson.dump(expected), MultiJson.dump(MultiJson.load(File.read(d)))
|
89
|
+
|
90
|
+
@c.update_digest
|
91
|
+
assert File.exist? d
|
92
|
+
assert_equal MultiJson.dump(expected), MultiJson.dump(MultiJson.load(File.read(d)))
|
93
|
+
end
|
94
|
+
|
95
|
+
end # TestCommandSpec
|
96
|
+
|
97
|
+
end # Test
|
98
|
+
end # Bixby
|