bixby-common 0.3.8
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.
- 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
|