azuki 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/README.md +71 -0
- data/bin/azuki +17 -0
- data/data/cacert.pem +3988 -0
- data/lib/azuki.rb +17 -0
- data/lib/azuki/auth.rb +339 -0
- data/lib/azuki/cli.rb +38 -0
- data/lib/azuki/client.rb +764 -0
- data/lib/azuki/client/azuki_postgresql.rb +141 -0
- data/lib/azuki/client/cisaurus.rb +26 -0
- data/lib/azuki/client/pgbackups.rb +113 -0
- data/lib/azuki/client/rendezvous.rb +108 -0
- data/lib/azuki/client/ssl_endpoint.rb +25 -0
- data/lib/azuki/command.rb +294 -0
- data/lib/azuki/command/account.rb +23 -0
- data/lib/azuki/command/accounts.rb +34 -0
- data/lib/azuki/command/addons.rb +305 -0
- data/lib/azuki/command/apps.rb +393 -0
- data/lib/azuki/command/auth.rb +86 -0
- data/lib/azuki/command/base.rb +230 -0
- data/lib/azuki/command/certs.rb +209 -0
- data/lib/azuki/command/config.rb +137 -0
- data/lib/azuki/command/db.rb +218 -0
- data/lib/azuki/command/domains.rb +85 -0
- data/lib/azuki/command/drains.rb +46 -0
- data/lib/azuki/command/fork.rb +164 -0
- data/lib/azuki/command/git.rb +64 -0
- data/lib/azuki/command/help.rb +179 -0
- data/lib/azuki/command/keys.rb +115 -0
- data/lib/azuki/command/labs.rb +147 -0
- data/lib/azuki/command/logs.rb +45 -0
- data/lib/azuki/command/maintenance.rb +61 -0
- data/lib/azuki/command/pg.rb +269 -0
- data/lib/azuki/command/pgbackups.rb +329 -0
- data/lib/azuki/command/plugins.rb +110 -0
- data/lib/azuki/command/ps.rb +232 -0
- data/lib/azuki/command/regions.rb +22 -0
- data/lib/azuki/command/releases.rb +124 -0
- data/lib/azuki/command/run.rb +180 -0
- data/lib/azuki/command/sharing.rb +89 -0
- data/lib/azuki/command/ssl.rb +43 -0
- data/lib/azuki/command/stack.rb +62 -0
- data/lib/azuki/command/status.rb +51 -0
- data/lib/azuki/command/update.rb +47 -0
- data/lib/azuki/command/version.rb +23 -0
- data/lib/azuki/deprecated.rb +5 -0
- data/lib/azuki/deprecated/help.rb +38 -0
- data/lib/azuki/distribution.rb +9 -0
- data/lib/azuki/excon.rb +9 -0
- data/lib/azuki/helpers.rb +517 -0
- data/lib/azuki/helpers/azuki_postgresql.rb +165 -0
- data/lib/azuki/helpers/log_displayer.rb +70 -0
- data/lib/azuki/plugin.rb +163 -0
- data/lib/azuki/updater.rb +171 -0
- data/lib/azuki/version.rb +3 -0
- data/lib/vendor/azuki/okjson.rb +598 -0
- data/spec/azuki/auth_spec.rb +256 -0
- data/spec/azuki/client/azuki_postgresql_spec.rb +71 -0
- data/spec/azuki/client/pgbackups_spec.rb +43 -0
- data/spec/azuki/client/rendezvous_spec.rb +62 -0
- data/spec/azuki/client/ssl_endpoint_spec.rb +48 -0
- data/spec/azuki/client_spec.rb +564 -0
- data/spec/azuki/command/addons_spec.rb +601 -0
- data/spec/azuki/command/apps_spec.rb +351 -0
- data/spec/azuki/command/auth_spec.rb +38 -0
- data/spec/azuki/command/base_spec.rb +109 -0
- data/spec/azuki/command/certs_spec.rb +178 -0
- data/spec/azuki/command/config_spec.rb +144 -0
- data/spec/azuki/command/db_spec.rb +110 -0
- data/spec/azuki/command/domains_spec.rb +87 -0
- data/spec/azuki/command/drains_spec.rb +34 -0
- data/spec/azuki/command/fork_spec.rb +56 -0
- data/spec/azuki/command/git_spec.rb +144 -0
- data/spec/azuki/command/help_spec.rb +93 -0
- data/spec/azuki/command/keys_spec.rb +120 -0
- data/spec/azuki/command/labs_spec.rb +100 -0
- data/spec/azuki/command/logs_spec.rb +60 -0
- data/spec/azuki/command/maintenance_spec.rb +51 -0
- data/spec/azuki/command/pg_spec.rb +236 -0
- data/spec/azuki/command/pgbackups_spec.rb +307 -0
- data/spec/azuki/command/plugins_spec.rb +104 -0
- data/spec/azuki/command/ps_spec.rb +195 -0
- data/spec/azuki/command/releases_spec.rb +130 -0
- data/spec/azuki/command/run_spec.rb +83 -0
- data/spec/azuki/command/sharing_spec.rb +59 -0
- data/spec/azuki/command/stack_spec.rb +46 -0
- data/spec/azuki/command/status_spec.rb +48 -0
- data/spec/azuki/command/version_spec.rb +16 -0
- data/spec/azuki/command_spec.rb +211 -0
- data/spec/azuki/helpers/azuki_postgresql_spec.rb +155 -0
- data/spec/azuki/helpers_spec.rb +48 -0
- data/spec/azuki/plugin_spec.rb +172 -0
- data/spec/azuki/updater_spec.rb +44 -0
- data/spec/helper/legacy_help.rb +16 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +224 -0
- data/spec/support/display_message_matcher.rb +49 -0
- data/spec/support/openssl_mock_helper.rb +8 -0
- metadata +211 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
require "azuki/helpers"
|
|
2
|
+
|
|
3
|
+
module Azuki::Helpers::AzukiPostgresql
|
|
4
|
+
|
|
5
|
+
extend self
|
|
6
|
+
extend Azuki::Helpers
|
|
7
|
+
|
|
8
|
+
class Attachment
|
|
9
|
+
attr_reader :config_var, :resource_name, :url, :addon, :plan
|
|
10
|
+
def initialize(raw)
|
|
11
|
+
@raw = raw
|
|
12
|
+
@config_var = raw['config_var']
|
|
13
|
+
@resource_name = raw['resource']['name']
|
|
14
|
+
@url = raw['resource']['value']
|
|
15
|
+
@addon, @plan = raw['resource']['type'].split(':')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def starter_plan?
|
|
19
|
+
plan =~ /dev|basic/
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def display_name
|
|
23
|
+
config_var + (primary_attachment? ? " (DATABASE_URL)" : '')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def primary_attachment!
|
|
27
|
+
@primary_attachment = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def primary_attachment?
|
|
31
|
+
@primary_attachment
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def hpg_addon_name
|
|
36
|
+
if ENV['SHOGUN']
|
|
37
|
+
"shogun-#{ENV['SHOGUN']}"
|
|
38
|
+
else
|
|
39
|
+
ENV['AZUKI_POSTGRESQL_ADDON_NAME'] || 'azuki-postgresql'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def app_config_vars
|
|
44
|
+
@app_config_vars ||= api.get_config_vars(app).body
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def app_attachments
|
|
48
|
+
@app_attachments ||= api.get_attachments(app).body.map { |raw| Attachment.new(raw) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def hpg_databases
|
|
52
|
+
return @hpg_databases if @hpg_databases
|
|
53
|
+
pairs = app_attachments.select {|att|
|
|
54
|
+
att.addon == hpg_addon_name
|
|
55
|
+
}.map { |att|
|
|
56
|
+
[att.config_var, att]
|
|
57
|
+
}
|
|
58
|
+
@hpg_databases = Hash[ pairs ]
|
|
59
|
+
|
|
60
|
+
if find_database_url_real_attachment
|
|
61
|
+
@hpg_databases['DATABASE_URL'] = find_database_url_real_attachment
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if app_config_vars['SHARED_DATABASE_URL']
|
|
65
|
+
@hpg_databases['SHARED_DATABASE'] = Attachment.new({
|
|
66
|
+
'config_var' => 'SHARED_DATABASE',
|
|
67
|
+
'resource' => {
|
|
68
|
+
'name' => 'SHARED_DATABASE',
|
|
69
|
+
'value' => app_config_vars['SHARED_DATABASE_URL'],
|
|
70
|
+
'type' => 'shared:database'
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return @hpg_databases
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def resource_url(resource)
|
|
79
|
+
api.get_resource(resource).body['value']
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def forget_config!
|
|
83
|
+
@hpg_databases = nil
|
|
84
|
+
@app_config_vars = nil
|
|
85
|
+
@app_attachments = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def find_database_url_real_attachment
|
|
89
|
+
raw_primary_db_url = app_config_vars['DATABASE_URL']
|
|
90
|
+
return unless raw_primary_db_url
|
|
91
|
+
|
|
92
|
+
primary_db_url = raw_primary_db_url.split("?").first
|
|
93
|
+
return unless primary_db_url && !primary_db_url.empty?
|
|
94
|
+
|
|
95
|
+
real_config = app_config_vars.detect {|k,v| k != 'DATABASE_URL' && v == primary_db_url }
|
|
96
|
+
if real_config
|
|
97
|
+
real = hpg_databases[real_config.first]
|
|
98
|
+
real.primary_attachment! if real
|
|
99
|
+
return real
|
|
100
|
+
else
|
|
101
|
+
return nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def match_attachments_by_name(name)
|
|
106
|
+
return [] if name.empty?
|
|
107
|
+
return [name] if hpg_databases[name]
|
|
108
|
+
hpg_databases.keys.grep(%r{#{ name }}i)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def hpg_resolve(name, default=nil)
|
|
112
|
+
name = '' if name.nil?
|
|
113
|
+
name = 'DATABASE_URL' if name == 'DATABASE'
|
|
114
|
+
|
|
115
|
+
if hpg_databases.empty?
|
|
116
|
+
error("Your app has no databases.")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
found_attachment = nil
|
|
120
|
+
candidates = match_attachments_by_name(name)
|
|
121
|
+
if default && name.empty? && app_config_vars[default]
|
|
122
|
+
found_attachment = hpg_databases[default]
|
|
123
|
+
elsif candidates.size == 1
|
|
124
|
+
found_attachment = hpg_databases[candidates.first]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if found_attachment.nil?
|
|
128
|
+
error("Unknown database#{': ' + name unless name.empty?}. Valid options are: #{hpg_databases.keys.sort.join(", ")}")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
return found_attachment
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def hpg_translate_fork_and_follow(addon, config)
|
|
135
|
+
if addon =~ /^#{hpg_addon_name}/
|
|
136
|
+
%w[fork follow].each do |opt|
|
|
137
|
+
if val = config[opt]
|
|
138
|
+
unless val.is_a?(String)
|
|
139
|
+
error("--#{opt} requires a database argument.")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
uri = URI.parse(val) rescue nil
|
|
143
|
+
if uri && uri.scheme
|
|
144
|
+
argument_url = uri.to_s
|
|
145
|
+
else
|
|
146
|
+
attachment = hpg_resolve(val)
|
|
147
|
+
if attachment.starter_plan?
|
|
148
|
+
error("#{opt.tr 'f', 'F'} is only available on production databases.")
|
|
149
|
+
end
|
|
150
|
+
argument_url = attachment.url
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
config[opt] = argument_url
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
def hpg_promote(url)
|
|
162
|
+
api.put_config_vars(app, "DATABASE_URL" => url)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require "azuki/helpers"
|
|
2
|
+
|
|
3
|
+
module Azuki::Helpers
|
|
4
|
+
class LogDisplayer
|
|
5
|
+
|
|
6
|
+
include Azuki::Helpers
|
|
7
|
+
|
|
8
|
+
attr_reader :azuki, :app, :opts
|
|
9
|
+
|
|
10
|
+
def initialize(azuki, app, opts)
|
|
11
|
+
@azuki, @app, @opts = azuki, app, opts
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def display_logs
|
|
15
|
+
@assigned_colors = {}
|
|
16
|
+
@line_start = true
|
|
17
|
+
@token = nil
|
|
18
|
+
|
|
19
|
+
azuki.read_logs(app, opts) do |chunk|
|
|
20
|
+
unless chunk.empty?
|
|
21
|
+
if STDOUT.isatty && ENV.has_key?("TERM")
|
|
22
|
+
display(colorize(chunk))
|
|
23
|
+
else
|
|
24
|
+
display(chunk)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
rescue Errno::EPIPE
|
|
29
|
+
rescue Interrupt => interrupt
|
|
30
|
+
if STDOUT.isatty && ENV.has_key?("TERM")
|
|
31
|
+
display("\e[0m")
|
|
32
|
+
end
|
|
33
|
+
raise(interrupt)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
COLORS = %w( cyan yellow green magenta red )
|
|
37
|
+
COLOR_CODES = {
|
|
38
|
+
"red" => 31,
|
|
39
|
+
"green" => 32,
|
|
40
|
+
"yellow" => 33,
|
|
41
|
+
"magenta" => 35,
|
|
42
|
+
"cyan" => 36,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def colorize(chunk)
|
|
46
|
+
lines = []
|
|
47
|
+
chunk.split("\n").map do |line|
|
|
48
|
+
if parsed_line = parse_log(line)
|
|
49
|
+
header, identifier, body = parsed_line
|
|
50
|
+
@assigned_colors[identifier] ||= COLORS[@assigned_colors.size % COLORS.size]
|
|
51
|
+
lines << [
|
|
52
|
+
"\e[#{COLOR_CODES[@assigned_colors[identifier]]}m",
|
|
53
|
+
header,
|
|
54
|
+
"\e[0m",
|
|
55
|
+
body,
|
|
56
|
+
].join("")
|
|
57
|
+
elsif not line.empty?
|
|
58
|
+
lines << line
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
lines.join("\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def parse_log(log)
|
|
65
|
+
return unless parsed = log.match(/^(.*?\[(\w+)([\d\.]+)?\]:)(.*)?$/)
|
|
66
|
+
[1, 2, 4].map { |i| parsed[i] }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/azuki/plugin.rb
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# based on the Rails Plugin
|
|
2
|
+
|
|
3
|
+
module Azuki
|
|
4
|
+
class Plugin
|
|
5
|
+
include Azuki::Helpers
|
|
6
|
+
extend Azuki::Helpers
|
|
7
|
+
|
|
8
|
+
class ErrorUpdatingSymlinkPlugin < StandardError; end
|
|
9
|
+
|
|
10
|
+
DEPRECATED_PLUGINS = %w(
|
|
11
|
+
azuki-cedar
|
|
12
|
+
azuki-certs
|
|
13
|
+
azuki-credentials
|
|
14
|
+
azuki-kill
|
|
15
|
+
azuki-labs
|
|
16
|
+
azuki-logging
|
|
17
|
+
azuki-netrc
|
|
18
|
+
azuki-pgdumps
|
|
19
|
+
azuki-postgresql
|
|
20
|
+
azuki-releases
|
|
21
|
+
azuki-shared-postgresql
|
|
22
|
+
azuki-sql-console
|
|
23
|
+
azuki-status
|
|
24
|
+
azuki-stop
|
|
25
|
+
azuki-suggest
|
|
26
|
+
pgbackups-automate
|
|
27
|
+
pgcmd
|
|
28
|
+
azuki-fork
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
attr_reader :name, :uri
|
|
32
|
+
|
|
33
|
+
def self.directory
|
|
34
|
+
File.expand_path("#{home_directory}/.azuki/plugins")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.list
|
|
38
|
+
Dir["#{directory}/*"].sort.map do |folder|
|
|
39
|
+
File.basename(folder)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.load!
|
|
44
|
+
list.each do |plugin|
|
|
45
|
+
check_for_deprecation(plugin)
|
|
46
|
+
next if skip_plugins.include?(plugin)
|
|
47
|
+
load_plugin(plugin)
|
|
48
|
+
end
|
|
49
|
+
# check to see if we are using ddollar/azuki-accounts
|
|
50
|
+
if list.include?('azuki-accounts') && Azuki::Auth.methods.include?(:fetch_from_account)
|
|
51
|
+
# setup netrc to match the default, if one exists
|
|
52
|
+
if default_account = %x{ git config azuki.account }.chomp
|
|
53
|
+
account = Azuki::Auth.extract_account rescue nil
|
|
54
|
+
if account && Azuki::Auth.read_credentials != [Azuki::Auth.user, Azuki::Auth.password]
|
|
55
|
+
Azuki::Auth.credentials = [Azuki::Auth.user, Azuki::Auth.password]
|
|
56
|
+
Azuki::Auth.write_credentials
|
|
57
|
+
load("#{File.dirname(__FILE__)}/command/accounts.rb")
|
|
58
|
+
# kill memoization in case '--account' was passed
|
|
59
|
+
Azuki::Auth.instance_variable_set(:@account, nil)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.load_plugin(plugin)
|
|
66
|
+
begin
|
|
67
|
+
folder = "#{self.directory}/#{plugin}"
|
|
68
|
+
$: << "#{folder}/lib" if File.directory? "#{folder}/lib"
|
|
69
|
+
load "#{folder}/init.rb" if File.exists? "#{folder}/init.rb"
|
|
70
|
+
rescue ScriptError, StandardError => error
|
|
71
|
+
styled_error(error, "Unable to load plugin #{plugin}.")
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.remove_plugin(plugin)
|
|
77
|
+
FileUtils.rm_rf("#{self.directory}/#{plugin}")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.check_for_deprecation(plugin)
|
|
81
|
+
return unless STDIN.isatty
|
|
82
|
+
|
|
83
|
+
if DEPRECATED_PLUGINS.include?(plugin)
|
|
84
|
+
if confirm "The plugin #{plugin} has been deprecated. Would you like to remove it? (y/N)"
|
|
85
|
+
remove_plugin(plugin)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.skip_plugins
|
|
91
|
+
@skip_plugins ||= ENV["SKIP_PLUGINS"].to_s.split(/[ ,]/)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def initialize(uri)
|
|
95
|
+
@uri = uri
|
|
96
|
+
guess_name(uri)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def to_s
|
|
100
|
+
name
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def path
|
|
104
|
+
"#{self.class.directory}/#{name}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def install
|
|
108
|
+
if File.directory?(path)
|
|
109
|
+
uninstall
|
|
110
|
+
end
|
|
111
|
+
FileUtils.mkdir_p(self.class.directory)
|
|
112
|
+
Dir.chdir(self.class.directory) do
|
|
113
|
+
git("clone #{uri}")
|
|
114
|
+
unless $?.success?
|
|
115
|
+
FileUtils.rm_rf path
|
|
116
|
+
return false
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def uninstall
|
|
123
|
+
ensure_plugin_exists
|
|
124
|
+
FileUtils.rm_r(path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def update
|
|
128
|
+
ensure_plugin_exists
|
|
129
|
+
if File.symlink?(path)
|
|
130
|
+
raise Azuki::Plugin::ErrorUpdatingSymlinkPlugin
|
|
131
|
+
else
|
|
132
|
+
Dir.chdir(path) do
|
|
133
|
+
unless git('config --get branch.master.remote').empty?
|
|
134
|
+
message = git("pull")
|
|
135
|
+
unless $?.success?
|
|
136
|
+
error("Unable to update #{name}.\n" + message)
|
|
137
|
+
end
|
|
138
|
+
else
|
|
139
|
+
error(<<-ERROR)
|
|
140
|
+
#{name} is a legacy plugin installation.
|
|
141
|
+
Enable updating by reinstalling with `azuki plugins:install`.
|
|
142
|
+
ERROR
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
|
|
150
|
+
def ensure_plugin_exists
|
|
151
|
+
unless File.directory?(path)
|
|
152
|
+
error("#{name} plugin not found.")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def guess_name(url)
|
|
157
|
+
@name = File.basename(url)
|
|
158
|
+
@name = File.basename(File.dirname(url)) if @name.empty?
|
|
159
|
+
@name.gsub!(/\.git$/, '') if @name =~ /\.git$/
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "azuki/helpers"
|
|
4
|
+
|
|
5
|
+
module Azuki
|
|
6
|
+
module Updater
|
|
7
|
+
|
|
8
|
+
def self.error(message)
|
|
9
|
+
raise Azuki::Command::CommandFailed.new(message)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.updating_lock_path
|
|
13
|
+
File.join(Azuki::Helpers.home_directory, ".azuki", "updating")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.installed_client_path
|
|
17
|
+
File.expand_path("../../..", __FILE__)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.updated_client_path
|
|
21
|
+
File.join(Azuki::Helpers.home_directory, ".azuki", "client")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.latest_local_version
|
|
25
|
+
installed_version = client_version_from_path(installed_client_path)
|
|
26
|
+
updated_version = client_version_from_path(updated_client_path)
|
|
27
|
+
if compare_versions(updated_version, installed_version) > 0
|
|
28
|
+
updated_version
|
|
29
|
+
else
|
|
30
|
+
installed_version
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.client_version_from_path(path)
|
|
35
|
+
version_file = File.join(path, "lib/azuki/version.rb")
|
|
36
|
+
if File.exists?(version_file)
|
|
37
|
+
File.read(version_file).match(/VERSION = "([^"]+)"/)[1]
|
|
38
|
+
else
|
|
39
|
+
'0.0.0'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.disable(message=nil)
|
|
44
|
+
@disable = message if message
|
|
45
|
+
@disable
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.check_disabled!
|
|
49
|
+
if disable
|
|
50
|
+
Azuki::Helpers.error(disable)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.wait_for_lock(path, wait_for=5, check_every=0.5)
|
|
55
|
+
start = Time.now.to_i
|
|
56
|
+
while File.exists?(path)
|
|
57
|
+
sleep check_every
|
|
58
|
+
if (Time.now.to_i - start) > wait_for
|
|
59
|
+
Azuki::Helpers.error "Unable to acquire update lock"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
begin
|
|
63
|
+
FileUtils.touch path
|
|
64
|
+
ret = yield
|
|
65
|
+
ensure
|
|
66
|
+
FileUtils.rm_f path
|
|
67
|
+
end
|
|
68
|
+
ret
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.autoupdate?
|
|
72
|
+
true
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.update(url, autoupdate=false)
|
|
76
|
+
wait_for_lock(updating_lock_path, 5) do
|
|
77
|
+
require "excon"
|
|
78
|
+
require "azuki"
|
|
79
|
+
require "azuki/excon"
|
|
80
|
+
require "tmpdir"
|
|
81
|
+
require "zip/zip"
|
|
82
|
+
|
|
83
|
+
latest_version = Excon.get_with_redirect("http://assets.azukiapp.com/azuki-client/VERSION", :nonblock => false).body.chomp
|
|
84
|
+
|
|
85
|
+
if compare_versions(latest_version, latest_local_version) > 0
|
|
86
|
+
Dir.mktmpdir do |download_dir|
|
|
87
|
+
File.open("#{download_dir}/azuki.zip", "wb") do |file|
|
|
88
|
+
file.print Excon.get_with_redirect(url, :nonblock => false).body
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
hash = Digest::SHA256.file("#{download_dir}/azuki.zip").hexdigest
|
|
92
|
+
official_hash = Excon.get_with_redirect("https://toolbelt.azukiapp.com/update/hash", :nonblock => false).body.chomp
|
|
93
|
+
|
|
94
|
+
error "Update hash signature mismatch" unless hash == official_hash
|
|
95
|
+
|
|
96
|
+
Zip::ZipFile.open("#{download_dir}/azuki.zip") do |zip|
|
|
97
|
+
zip.each do |entry|
|
|
98
|
+
target = File.join(download_dir, entry.to_s)
|
|
99
|
+
FileUtils.mkdir_p File.dirname(target)
|
|
100
|
+
zip.extract(entry, target) { true }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
FileUtils.rm "#{download_dir}/azuki.zip"
|
|
105
|
+
|
|
106
|
+
old_version = latest_local_version
|
|
107
|
+
new_version = client_version_from_path(download_dir)
|
|
108
|
+
|
|
109
|
+
if compare_versions(new_version, old_version) < 0 && !autoupdate
|
|
110
|
+
Azuki::Helpers.error("Installed version (#{old_version}) is newer than the latest available update (#{new_version})")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
FileUtils.rm_rf updated_client_path
|
|
114
|
+
FileUtils.mkdir_p File.dirname(updated_client_path)
|
|
115
|
+
FileUtils.cp_r download_dir, updated_client_path
|
|
116
|
+
|
|
117
|
+
new_version
|
|
118
|
+
end
|
|
119
|
+
else
|
|
120
|
+
false # already up to date
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
ensure
|
|
124
|
+
FileUtils.rm_f(updating_lock_path)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.compare_versions(first_version, second_version)
|
|
128
|
+
first_version.split('.').map {|part| Integer(part) rescue part} <=> second_version.split('.').map {|part| Integer(part) rescue part}
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.inject_libpath
|
|
132
|
+
old_version = client_version_from_path(installed_client_path)
|
|
133
|
+
new_version = client_version_from_path(updated_client_path)
|
|
134
|
+
|
|
135
|
+
if compare_versions(new_version, old_version) > 0
|
|
136
|
+
$:.unshift File.join(updated_client_path, "lib")
|
|
137
|
+
vendored_gems = Dir[File.join(updated_client_path, "vendor", "gems", "*")]
|
|
138
|
+
vendored_gems.each do |vendored_gem|
|
|
139
|
+
$:.unshift File.join(vendored_gem, "lib")
|
|
140
|
+
end
|
|
141
|
+
load('azuki/updater.rb') # reload updated updater
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
background_update!
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.last_autoupdate_path
|
|
148
|
+
File.join(Azuki::Helpers.home_directory, ".azuki", "autoupdate.last")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def self.background_update!
|
|
152
|
+
# if we've updated in the last 300 seconds, dont try again
|
|
153
|
+
if File.exists?(last_autoupdate_path)
|
|
154
|
+
return if (Time.now.to_i - File.mtime(last_autoupdate_path).to_i) < 300
|
|
155
|
+
end
|
|
156
|
+
log_path = File.join(Azuki::Helpers.home_directory, '.azuki', 'autoupdate.log')
|
|
157
|
+
FileUtils.mkdir_p File.dirname(log_path)
|
|
158
|
+
azuki_binary = File.expand_path($0)
|
|
159
|
+
pid = if defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
|
|
160
|
+
fork do
|
|
161
|
+
exec("\"#{azuki_binary}\" update &> #{log_path} 2>&1")
|
|
162
|
+
end
|
|
163
|
+
else
|
|
164
|
+
spawn("\"#{azuki_binary}\" update", {:err => log_path, :out => log_path})
|
|
165
|
+
end
|
|
166
|
+
Process.detach(pid)
|
|
167
|
+
FileUtils.mkdir_p File.dirname(last_autoupdate_path)
|
|
168
|
+
FileUtils.touch last_autoupdate_path
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|